46 votes

java.lang.NullPointerException est levée en utilisant une référence de méthode mais pas une expression lambda

J'ai remarqué quelque chose de bizarre à propos des exceptions non gérées à l'aide de Java 8 méthode de référence. C'est mon code, à l'aide de l'expression lambda () -> s.toLowerCase():

public class Test {

    public static void main(String[] args) {
        testNPE(null);
    }

    private static void testNPE(String s) {
        Thread t = new Thread(() -> s.toLowerCase());
//        Thread t = new Thread(s::toLowerCase);
        t.setUncaughtExceptionHandler((t1, e) -> System.out.println("Exception!"));
        t.start();
    }
}

Il imprime "Exception", de sorte qu'il fonctionne bien. Mais quand je change Thread t le recours à une méthode de référence (même IntelliJ suggère que):

Thread t = new Thread(s::toLowerCase);

l'exception n'est pas d'être pris:

Exception in thread "main" java.lang.NullPointerException
    at Test.testNPE(Test.java:9)
    at Test.main(Test.java:4)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

Quelqu'un peut m'expliquer ce qui se passe ici?

55voto

Tunaki Points 2663

Ce comportement repose sur une subtile différence entre le processus d'évaluation de la méthode-les références et les expressions lambda.

À partir de la JLS au Moment de l'Exécution de l'Évaluation de la Méthode Références:

Tout d'abord, si la méthode de référence de l'expression commence par un ExpressionName ou Primaire, cette sous-expression est évaluée. Si la sous-expression évalue null, un NullPointerException est élevé, et la méthode de référence de l'expression se termine brusquement.

Avec le code suivant:

Thread t = new Thread(s::toLowerCase); // <-- s is null, NullPointerException thrown here
t.setUncaughtExceptionHandler((t1, e) -> System.out.println("Exception!"));

l'expression s est évaluée à l' null et une exception est levée exactement lorsque cette méthode de référence est évaluée. Cependant, à cette époque, aucun gestionnaire d'exception a été attaché, puisque ce code sera exécuté après.

Cela ne se produit pas dans le cas d'une expression lambda, parce que le lambda sera évalué en l'absence de son corps en cours d'exécution. À partir d'Exécution l'Évaluation des Expressions Lambda:

L'évaluation d'une expression lambda est distincte de l'exécution du corps de lambda.

Thread t = new Thread(() -> s.toLowerCase());
t.setUncaughtExceptionHandler((t1, e) -> System.out.println("Exception!"));

Même si s est null, l'expression lambda sera créé correctement. Ensuite, le gestionnaire d'exception sera joint, le thread va démarrer, lancer une exception, qui sera pris par le gestionnaire.


Comme une note, il semble Éclipse de Mars.2 a un petit bug concernant cette: même avec la méthode de référence, il appelle le gestionnaire d'exception. Eclipse n'est pas à jeter un NullPointerException à s::toLowerCase quand il le faut, donc de reporter l'exception plus tard, lorsque le gestionnaire d'exception a été ajoutée.

7voto

Nikem Points 826

Wow. Vous avez découvert quelque chose d'intéressant. Laissez-nous jeter un oeil à ce qui suit:

Function<String, String> stringStringFunction = String::toLowerCase;

Cela nous renvoie une fonction qui accepte sur le paramètre de type String et renvoie une autre String, ce qui est un des caractères du paramètre d'entrée. C'est un peu l'équivalent de l' s.toLowerCase()s est le paramètre d'entrée.

stringStringFunction(param) === param.toLowerCase()

Prochaine

Function<Locale, String> localeStringFunction = s::toLowerCase;

est une fonction à partir d' Locale de String. C'est l'équivalent d' s.toLowerCase(Locale) d'invocation de méthode. Il travaille, sous le capot, sur 2 paramètres: l'un est - s et un autre est certains paramètres régionaux. Si s est null, alors cette fonction de création jette un NullPointerException.

localeStringFunction(locale) === s.toLowerCase(locale)

La prochaine est

Runnable r = () -> s.toLowerCase()

Qui est une implémentation de l' Runnable interface qui, lorsqu'elles sont exécutées, l'appel de la méthode toLowerCase sur la chaîne donnée en s.

Donc dans votre cas

Thread t = new Thread(s::toLowerCase);

essaie de créer un nouveau Thread passant le résultat de l'invocation de l' s::toLowerCase pour elle. Mais cela jette un NPE à la fois. Même avant que le thread est démarré. Et donc, NPE est jeté dans votre thread en cours, pas de filetage t. C'est pourquoi votre gestionnaire d'exception n'est pas exécutée.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X