java.lang.NullPointerException jest rzucany przy użyciu referencji metody, ale nie wyrażenia lambda

Zauważyłem coś dziwnego w nieobsługiwanych wyjątkach przy użyciu Java 8 method reference. To jest mój kod, używając wyrażenia 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();
    }
}

Wyświetla "wyjątek", więc działa dobrze. Ale kiedy zmienię Thread t, Aby użyć metody-referencji (nawet IntelliJ sugeruje, że):

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

Wyjątek nie jest złapany:

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)
Czy ktoś może wyjaśnić, co tu się dzieje?
Author: Tunaki, 2016-05-24

2 answers

To zachowanie opiera się na subtelnej różnicy między procesem oceny referencji metody a wyrażeniami lambda.

From the JLS Run-Time Evaluation of Method References :

Po pierwsze, jeśli wyrażenie odniesienia do metody zaczyna się od nazwy wyrażenia lub wyrażenia podstawowego, to podwyrażenie jest obliczane. Jeśli podwyrażenie zostanie obliczone na null, zostanie wywołane NullPointerException, a wyrażenie odniesienia do metody zostanie uzupełnione nagle .

O następującym kodzie:

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

Wyrażenie s jest obliczane na null i wyjątek jest rzucany dokładnie wtedy, gdy ta metoda-Referencja jest obliczana. Jednak w tym czasie nie dołączono żadnej obsługi wyjątków, ponieważ kod ten byłby wykonywany po.

Tak się nie dzieje w przypadku wyrażenia lambda, ponieważ lambda będzie oceniana bez wykonywania jej ciała. Z oceny Czasu Pracy Lambda Wyrażenia :

Ocena wyrażenia lambda różni się od wykonania ciała lambda.

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

Nawet jeśli s jest null, wyrażenie lambda zostanie poprawnie utworzone. Następnie zostanie dołączony moduł obsługi wyjątków, wątek rozpocznie się, rzucając wyjątek, który zostanie przechwycony przez moduł obsługi.


Na marginesie, wydaje się, że zaćmienie Marsa.2 ma mały błąd dotyczący tego: nawet z method-reference, wywołuje wyjątek handler. Eclipse nie rzuca NullPointerException w s::toLowerCase kiedy powinno, więc odkłada wyjątek później, kiedy dodano obsługę wyjątków.

 56
Author: Tunaki,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-07 14:20:35

Wow. Odkryłeś coś interesującego. Spójrzmy na:

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

Zwraca nam funkcję, która przyjmuje parametr typu String i zwraca inny String, który jest małą literą parametru wejściowego. Jest to w pewnym sensie odpowiednik s.toLowerCase(), gdzie s jest parametrem wejściowym.

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

Następna

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

Jest funkcją od Locale do String. Jest to równoważne wywołaniu metody s.toLowerCase(Locale). Działa, pod maską, na 2 parametry: jeden to s, a drugi to jakieś locale. Jeśli s jest null, to utworzenie tej funkcji rzuca NullPointerException.

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

Następny jest

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

Która jest implementacją interfejsu Runnable, który po wykonaniu wywoła metodę toLowerCase na podanym łańcuchu s.

Więc w Twoim przypadku

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

Próbuje utworzyć nowy Thread przekazując do niego wynik wywołania s::toLowerCase. Ale to rzuca NPE na raz. Nawet przed rozpoczęciem wątku. I tak NPE jest wrzucone do bieżącego wątku, a nie z wewnątrz wątku t. Dlatego obsługa wyjątków nie jest wykonywana.

 7
Author: Nikem,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-24 12:32:08