J'ai remarqué quelque chose de bizarre à propos des exceptions non gérées en utilisant Java 8. C'est mon code, en utilisant 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", donc cela fonctionne très bien. Mais quand je change Thread t
pour utiliser une référence de méthode (même IntelliJ le suggère):
Thread t = new Thread(s::toLowerCase);
l'exception n'est pas interceptée:
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-il expliquer ce qui se passe ici?
Ce comportement repose sur une différence subtile entre le processus d'évaluation des références de méthode et les expressions lambda.
À partir du JLS Évaluation au moment de l'exécution des références de méthode :
Premièrement, si l'expression de référence de méthode commence par un ExpressionName ou un Primary, cette sous-expression est évaluée. Si la sous-expression est évaluée à
null
, unNullPointerException
est levé et l'expression de référence de méthode 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 en null
et une exception est levée exactement lorsque cette référence de méthode est évaluée. Cependant, à cette époque, aucun gestionnaire d'exception n'était attaché, car ce code serait exécuté après.
Cela ne se produit pas dans le cas d'une expression lambda, car la lambda sera évaluée sans que son corps soit exécuté. De Évaluation au moment de l'exécution des expressions Lambda :
L'évaluation d'une expression lambda est distincte de l'exécution du corps 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 correctement créée. Ensuite, le gestionnaire d'exceptions sera attaché, le thread démarrera, lançant une exception, qui sera interceptée par le gestionnaire.
En remarque, il semble qu'Eclipse Mars.2 ait un petit bogue à ce sujet: même avec la référence de méthode, il appelle le gestionnaire d'exceptions. Eclipse ne lance pas un NullPointerException
sur s::toLowerCase
quand il le devrait, différant ainsi l'exception plus tard, lorsque le gestionnaire d'exceptions a été ajouté.
Sensationnel. Vous avez découvert quelque chose d'intéressant. Voyons ce qui suit:
Function<String, String> stringStringFunction = String::toLowerCase;
Cela nous renvoie une fonction qui accepte un paramètre de type String
et renvoie un autre String
, qui est une minuscule du paramètre d'entrée. C'est quelque peu équivalent à la s.toLowerCase()
, où s
est le paramètre d'entrée.
stringStringFunction(param) === param.toLowerCase()
Prochain
Function<Locale, String> localeStringFunction = s::toLowerCase;
est une fonction de Locale
à String
. Cela équivaut à l'invocation de la méthode s.toLowerCase(Locale)
. Cela fonctionne, sous le capot, sur 2 paramètres: l'un est s
et un autre est une locale. Si s
est null
, cette création de fonction renvoie un NullPointerException
.
localeStringFunction(locale) === s.toLowerCase(locale)
Vient ensuite
Runnable r = () -> s.toLowerCase()
Ce qui est une implémentation de l'interface Runnable
qui, une fois exécutée, appellera la méthode toLowerCase
sur la chaîne donnée s
.
Donc dans ton cas
Thread t = new Thread(s::toLowerCase);
essaie de créer un nouveau Thread
en lui passant le résultat de l'invocation de s::toLowerCase
. Mais cela lance un NPE
à la fois. Même avant le démarrage du thread. Et donc NPE
est jeté dans votre thread actuel, pas depuis le thread t
. C'est pourquoi votre gestionnaire d'exceptions n'est pas exécuté.