2016-05-24 17 views
34

Ho notato qualcosa di strano sulle eccezioni non gestite utilizzando il riferimento al metodo Java 8. Questo è il mio codice, utilizzando l'espressione lambda () -> s.toLowerCase():java.lang.NullPointerException viene generato utilizzando un metodo-riferimento ma non un'espressione lambda

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(); 
    } 
} 

Esso stampa "Eccezione", in modo che funziona bene. Ma quando cambio Thread t di utilizzare un metodo di riferimento (anche IntelliJ suggerisce che):

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

l'eccezione non viene catturato:

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) 

qualcuno può spiegare cosa sta succedendo qui?

+0

Ho controllato Eclipse da solo e funziona perfettamente. Ideone.com lancia anche NPE –

+1

Controlla questo: https://ideone.com/nPvWex – FaNaJ

+2

lambda vengono valutati quando vengono eseguiti, i riferimenti alle funzioni vengono valutati quando dichiarati. – njzk2

risposta

44

Questo comportamento si basa su una sottile differenza tra il processo di valutazione dei riferimenti di metodo e le espressioni lambda.

Dal JLS Run-Time Evaluation of Method References:

Innanzitutto, se l'espressione metodo di riferimento inizia con un ExpressionName o un contatto principale, questa sottoespressione viene valutata. Se la sottoespressione viene valutata su null, viene generato un valore NullPointerException e l'espressione di riferimento del metodo viene completata improvvisamente.

Con questo codice:

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

l'espressione s viene valutata per null e viene generata un'eccezione esattamente quando si valuta che il metodo riferimento. Tuttavia, in quel momento, non è stato associato alcun gestore di eccezioni, poiché questo codice sarebbe stato eseguito dopo.

Ciò non accade nel caso di un'espressione lambda, poiché la lambda verrà valutata senza esecuzione del suo corpo. Da Run-Time Evaluation of Lambda Expressions:

La valutazione di un'espressione lambda è diversa dall'esecuzione del corpo lambda.

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

Anche se s è null, l'espressione lambda verrà creata correttamente. Quindi verrà allegato il gestore delle eccezioni, il thread inizierà, generando un'eccezione che verrà catturata dal gestore.


Come un lato nota, sembra Eclipse Mars.2 ha un piccolo bug per quanto riguarda questo: anche con il metodo di riferimento, si invoca il gestore di eccezioni. Eclipse non sta lanciando uno NullPointerException allo s::toLowerCase quando dovrebbe, rinviando quindi l'eccezione in seguito, quando è stato aggiunto il gestore di eccezioni.

+4

Un modo più teorico per leggere questo è: le espressioni vengono valutate in "forme normali" (cioè i valori). Accade così che * le forme normali di * lambda * non * valutino il corpo fino a quando non vengono chiamate (noto anche come forma normale della testa debole WHNF). Ciò significa che il valore "errore" è diverso da "() -> errore" perché quest'ultimo genera l'errore solo quando si chiama la funzione. Questo trucco è anche usato per scrivere i combinatori di punti fissi in linguaggi desiderosi: prendi il lazy combinator 'fix f = f (fix f)' e aggiungi un'astrazione lambda per introdurre lazyness 'fix f = x -> f (fix f) x' . – Bakuriu

+4

Vedere anche ["Qual è l'espressione lambda equivalente per' System.out :: println' "] (http://stackoverflow.com/a/28025717/2711488) – Holger

+12

Non solo l'eccezione si verifica prima del gestore di eccezioni non rilevate è impostato, l'eccezione si verifica nel thread principale invece di 't'. – Holger

6

Wow. Hai scoperto qualcosa di interessante.Cerchiamo di dare un'occhiata al seguente:

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

Questo ci restituisce una funzione che accetta il parametro di tipo String e restituisce un'altra String, che è una minuscola del parametro di ingresso. Questo è in qualche modo equivalente allo s.toLowerCase(), dove s è il parametro di input.

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

Successivo

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

è una funzione da Locale a String. Questo è equivalente al richiamo del metodo s.toLowerCase(Locale). Funziona, sotto il cofano, su 2 parametri: uno è s e un altro è un locale. Se s è null, la creazione di questa funzione genera uno NullPointerException.

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

successivo è

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

Quale è un'implementazione di Runnable interfaccia che, quando eseguiti, chiamerà metodo toLowerCase sul determinata stringa s.

Quindi nel tuo caso

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

tenta di creare un nuovo Thread passando il risultato della chiamata di s::toLowerCase ad esso. Ma questo genera uno NPE in una sola volta. Anche prima dell'avvio del thread. E quindi NPE viene inserito nel thread corrente, non dal thread interno t. Questo è il motivo per cui il tuo gestore di eccezioni non viene eseguito.

+3

"il risultato dell'invocazione di' s :: toLowerCase' "ha bisogno di un chiarimento. La creazione dell'istanza della funzione non è un'invocazione. – Holger

Problemi correlati