Lambda 捕获 vs 非捕获

8
请问为什么第一个lambda表达式会捕获变量,而第二个不会呢?
   Runnable createLambdaWithCapture() {
      return System.out::println;
   }    
   Runnable createLambdaWithApparentCapture() {
        return () -> System.out.println();
   }

感谢您的回答。我认为方法引用是lambda的“合成糖”。甚至IntelliJ IDEA建议将此方法引用替换为相同的非捕获lambda。 - Steven
@Holger,解释得很好。不知道为什么几乎每本关于lambda的书都没有深入探讨细节。他们只是说你可以用方法引用替换lamdba,并没有说可能会有一些陷阱。 - Steven
1
这些书的主要目的是简化,否则任何人都会直接阅读Java语言规范。但是在哪里划线是一个艰难的决定。不幸的是,有很多文档,特别是网页,似乎因为作者不知道更好的方法而显得邋遢。你知道,每个人都可以建立一个网页... - Holger
2个回答

8
第一个代码片段是捕获的,因为它在执行return语句时评估了System.out并且捕获相应的对象引用以在其Runnable#run实现中使用。
第二个代码片段生成了一个未被捕获的Runnable实例,因为System.out仅在该实例的run方法被调用时被评估。自return语句被执行以来,它可能已经改变。

3
在第一种方法中,System.out 在 return 语句中立即被计算。
相应的 lambda 表达式就像你将 System.out 分解为一个变量,然后变量成为一个有效的闭包:
Runnable createLambdaWithCapture() {
    PrintWriter foo = System.out;
    return () -> foo.println(); // foo is captured and effectively final
}

在第二种方法中,System.out(它是一个静态字段)不是final的,并且稍后可以在运行时更改。它直到调用Runnable::run才被调用。
System.out = aPrintStream;
Runnable runnable1 = createLambdaWithCapture();
Runnable runnable2 = createLambdaWithApparentCapture();
System.out = anotherPrintStream;
runnable1.run(); // prints to aPrintStream
runnable2.run(); // prints to anotherPrintStream

1
等价的lambda表达式为PrintWriter foo = Objects.requireNonNull(System.out); return () -> foo.println(); - Holger
@Holger,那可能在技术上是正确的,并且与您回答的其他问题相关,但它对于这个问题似乎不太重要,只会成为噪音。 - Novaterata
说到不相关但技术上是真的事情,System.out final,尽管它仍然可以改变。但是你必须调用 System.setOut(…) 来改变它。这种奇怪的现象是 Java 的历史遗产之一。 - Holger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接