java基礎總結(六十七)--Java8中一個超易踩的坑:方法引用與lambda表達式的區別

作者:劉仁鵬
參考資料:

  1. java.lang.NullPointerException is thrown using a method-reference but not a lambda expression
  2. Run-Time Evaluation of Method References
  3. 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");
        }
    }

}

  • 可能你會說,testNPEOfLambdatestNPEOfMethodRef的區別不過是一個使用了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

    lambda.png-23kB

2.爲什麼方法引用與lambda表達式不同

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,方法引用表達式就會突然結束。如果子表達式突然結束,那麼方法引用表達式也會因爲同樣的原因突然結束。

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
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章