作者:劉仁鵬
參考資料:
- java.lang.NullPointerException is thrown using a method-reference but not a lambda expression
- Run-Time Evaluation of Method References
- Run-Time Evaluation of Lambda Expressions
1.IDEA在誘導我寫bug
- 看一下下面的代碼,你認爲它的輸出會是什麼?
public class Test {
@org.junit.Test
public void test() throws InterruptedException {
testNPEOfLambda(null);
Thread.sleep(1000);
testNPEOfMethodRef(null);
}
private static void testNPEOfLambda(MyPrinter printer) {
testNPE(() -> printer.out());
}
private static void testNPEOfMethodRef(MyPrinter printer) {
testNPE(printer::out);
}
private static void testNPE(Runnable runnable) {
Thread t = new Thread(runnable);
t.setUncaughtExceptionHandler((t1, e) ->
System.out.println(t1.getName() + " Exception!"));
t.start();
}
static class MyPrinter {
void out() {
System.out.println("hello world");
}
}
}
- 可能你會說,testNPEOfLambda與testNPEOfMethodRef的區別不過是一個使用了lambda表達式,一個使用了方法引用,最終都是返回一個Runnable對象,結果肯定是一樣的,類似下面這樣:
//輸出:
Thread-0 Exception!
Thread-1 Exception!
- 讓我們來實際運行一下:
//輸出:
Thread-0 Exception!
java.lang.NullPointerException
at com.lpcoder.agile.base.Test.testNPEOfMethodRef(Test.java:17)
at com.lpcoder.agile.base.Test.test(Test.java:9)
...略
- 這個結果和我們一開始預料的不一樣啊,使用lambda表達式運行結果在意料之中,而使用方法引用則過早的就拋出了NPE。甚至IDEA都在我使用lambda表示式時給我提示:這裏可使用方法引用。強烈暗示我這兩種寫法的效果是相同的:
lambda.png-23kB
2.爲什麼方法引用與lambda表達式不同
- 讓我們看下java官方文檔中對方法引用的運行時求值的說明:
First, if the method reference expression begins with an ExpressionName or a Primary, this subexpression is evaluated. If the subexpression evaluates to null, a NullPointerException is raised, and the method reference expression completes abruptly. If the subexpression completes abruptly, the method reference expression completes abruptly for the same reason.
首先,如果方法引用表達式以ExpressionName或Primary開頭,那麼將對這個子表達式進行求值。如果子表達式的計算值爲null,就會引發NullPointerException,方法引用表達式就會突然結束。如果子表達式突然結束,那麼方法引用表達式也會因爲同樣的原因突然結束。
- 那爲什麼lambda表達式不會有這個問題呢,來看下對lambda表達式的運行時求值的說明:
Evaluation of a lambda expression is distinct from execution of the lambda body.
對lambda表達式的求值與執行lambda正文是不同的。
- 所以,方法引用和lambda表達式,是有區別的:方法引用會在運行時,會對::前的子表達式進行“預求值”,如果發現子表達式值爲null,則拋出NPE。而lambda表達式不會,lambda表達式在運行時只會如常返回一個FunctionInterface實例,只有當真正運行lambda正文時,纔會拋出NPE(緩求值)
3.總結
- 不要認爲方法引用和lambda表達式是等價的(即使IDEA暗示你如此)
- 方法引用會對::符號前的子表達式進行預求值,如果發現值爲null,會立即拋出NPE
- lambda表達式只有在真正運行lambda正文時,纔會拋出NPE
- 如果需要用到javaFunctionInterface的緩求值特性,使用lambda表達式,而不要使用方法引用,否則有提前拋出NPE的風險
作者:靈派coder
鏈接:https://www.jianshu.com/p/d3e69bd192c7
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。