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
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java 8 引入了 lambda 表达式,使得代码编写更加简洁和易读。 lambda 表达式是一个匿名函数,它没有名称,但有参数列表、函数体和可能的返回类型。它的语法如下: ``` (parameters) -> expression ``` 或者 ``` (parameters) -> { statements; } ``` 其,parameters 是一个逗号分隔的参数列表,expression一个只包含一个表达式的语句,statements 是一个包含多个语句的代码块。 举个例子,假设有一个 List,我们可以使用 lambda 表达式对其进行排序: ``` List<String> names = Arrays.asList("Amy", "Bob", "Charlie", "David"); Collections.sort(names, (String a, String b) -> a.compareTo(b)); ``` 这里,我们使用了 lambda 表达式来定义一个比较器,它会按照字符串的自然顺序进行排序。 lambda 表达式还可以与函数式接口一起使用,函数式接口是只有一个抽象方法的接口。举个例子,假设有一个函数式接口: ``` interface MyInterface { void doSomething(int a, int b); } ``` 我们可以使用 lambda 表达式来实现该接口: ``` MyInterface myInterface = (a, b) -> System.out.println("The sum is " + (a + b)); myInterface.doSomething(5, 7); // 输出 "The sum is 12" ``` 这里,我们使用了 lambda 表达式来实现 MyInterface 接口的 doSomething 方法,输出了两个整数的和。 总的来说,lambda 表达式是 Java 8 一个非常有用的特性,它可以帮助我们更加简洁地编写代码,并且与函数式接口结合使用,可以实现更加灵活的编程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值