pet*_*rov 25 java lambda closures java-8
我想我已经在JavaScript中遇到了这种经典情况.
通常程序员会希望下面的代码可以打印"Peter","Paul","Mary".
但事实并非如此.谁能解释为什么它在Java中以这种方式工作?
这个Java 8代码编译好并打印3次"Mary".
我想这是一个问题,它是如何在内心深处实施的,
但是......这不是表明底层实施的错误吗?
import java.util.List;
import java.util.ArrayList;
public class Test008 {
public static void main(String[] args) {
String[] names = { "Peter", "Paul", "Mary" };
List<Runnable> runners = new ArrayList<>();
int[] ind = {0};
for (int i = 0; i < names.length; i++){
ind[0] = i;
runners.add(() -> System.out.println(names[ind[0]]));
}
for (int k=0; k<runners.size(); k++){
runners.get(k).run();
}
}
}
Run Code Online (Sandbox Code Playgroud)
相反,如果我使用增强的for循环(同时添加Runnables),则捕获正确的(即所有不同的)值.
for (String name : names){
runners.add(() -> System.out.println(name));
}
Run Code Online (Sandbox Code Playgroud)
最后,如果我使用经典的for循环(同时添加Runnables),那么我会得到一个编译错误(这很有意义,因为变量i不是最终的或有效的最终).
for (int i = 0; i < names.length; i++){
runners.add(() -> System.out.println(names[i]));
}
Run Code Online (Sandbox Code Playgroud)
编辑:
我的观点是:为什么不是names[ind[0]]捕获的值(它在我添加Runnables时的值)?我执行lambda表达式时无所谓,对吧?我的意思是,好吧,在具有增强的for循环的版本中,我也稍后执行Runnables但是之前捕获了正确/不同的值(添加Runnables时).
换句话说,为什么Java 在捕获值时总是没有这个值/快照语义(如果我可以这样说)?它会更清洁,更有意义吗?
Bri*_*etz 44
换句话说,为什么Java在捕获值时总是没有这个值/快照语义(如果我可以这样说)?它会更清洁,更有意义吗?
Java lambdas 确实具有按值捕获的语义.
当你这样做时:
runners.add(() -> System.out.println(names[ind[0]]));
Run Code Online (Sandbox Code Playgroud)
这里的lambda捕获两个值:ind和names.这两个值恰好都是对象引用,但对象引用是一个类似'3'的值.当捕获的对象引用引用可变对象时,这可能会使事情变得混乱,因为它们在lambda捕获期间的状态和在lambda调用期间的状态可能不同.具体来说,ind引用的数组确实以这种方式改变,这是导致问题的原因.
lambda没有捕获的是表达式的值ind[0].相反,它捕获引用ind,并且在调用 lambda时执行解除引用ind[0].Lambdas从其词汇封闭范围内接近价值; ind是词法封闭范围内的值,但ind[0]不是.我们ind在评估时进一步捕获并使用它.
你在某种程度上期待lambda将完成整个堆中可通过捕获的引用访问的所有对象的完整快照,但这不是它的工作方式 - 这也不是很有意义.
简介:Lambdas按值捕获所有捕获的参数 - 包括对象引用.但是对象引用和它引用的对象不是一回事.如果捕获对可变对象的引用,则对象的状态可能在调用lambda时发生了更改.
Cla*_*ayn 14
我会说代码完全按照你的要求去做.
您创建"Runnable",在该位置打印名称ind[0].但是这个表达式会在你的第二个for循环中得到评估.在这一点上ind[0]=2.所以表达式打印"Mary".
Era*_*ran 10
创建lambda表达式时,不执行它.当你调用run每个for循环时,它只在你的第二个for循环中执行Runnable.
当执行第二个循环时,ind[0]包含2,因此在执行所有run方法时打印"Mary" .
编辑:
增强的for循环行为有所不同,因为在该代码片段中,lambda表达式保存对String实例的引用,并且String是不可变的.如果更改String为StringBuilder,则可以使用增强的for循环构建示例,该示例还会打印所引用实例的最终值:
StringBuilder[] names = { new StringBuilder().append ("Peter"), new StringBuilder().append ("Paul"), new StringBuilder().append ("Mary") };
List<Runnable> runners = new ArrayList<>();
for (StringBuilder name : names){
runners.add(() -> System.out.println(name));
name.setLength (0);
name.append ("Mary");
}
for (int k=0; k<runners.size(); k++){
runners.get(k).run();
}
Run Code Online (Sandbox Code Playgroud)
输出:
Mary
Mary
Mary
Run Code Online (Sandbox Code Playgroud)
这是工作完美..而调用println方法ind[0]是2,你使用的是常见的变量和函数调用之前增加它的价值.因此它将始终打印Mary.您可以执行以下操作来检查
for (int i = 0; i < names.length; i++) {
final int j = i;
runners.add(() -> System.out.println(names[j]));
}
Run Code Online (Sandbox Code Playgroud)
这将根据需要打印所有名称
或在ind当地宣布
for (int i = 0; i < names.length; i++) {
final int[] ind = { 0 };
ind[0] = i;
runners.add(() -> System.out.println(names[ind[0]]));
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
3390 次 |
| 最近记录: |