作者:刘仁鹏
参考资料:
- 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的风险
end
网友评论