Lambda 表达式的工作方式类似于回调。在代码中传递它们的那一刻,它们“存储”它们操作所需的任何外部值(或引用)(就好像这些值在函数调用中作为参数传递一样)。这只是对开发人员隐藏的)。在第一个示例中,您可以通过存储到单独的变量(如 d)来解决此问题:k
for (int k = 0; k < 10; k++) {
final int d = k
new Thread(() -> System.out.println(d)).start();
}
实际上意味着,在上面的示例中,您可以省略“final”关键字,因为它实际上是final,因为它在其范围内永远不会更改。final
d
for
环路以不同的方式运行。它们是迭代代码(而不是回调)。它们在各自的范围内工作,并且可以在自己的堆栈上使用所有变量。这意味着,循环的代码块是外部代码块的一部分。for
至于你强调的问题:
增强型循环不与常规索引计数器一起运行,至少不能直接运行。增强的循环(在非数组上)创建一个隐藏的迭代器。您可以通过以下方式对此进行测试:for
for
Collection<String> mySet = new HashSet<>();
mySet.addAll(Arrays.asList("A", "B", "C"));
for (String myString : mySet) {
if (myString.equals("B")) {
mySet.remove(myString);
}
}
上面的示例将导致并发模式异常。这是由于迭代器注意到基础集合在执行期间已更改。但是,在您的示例中,外部循环创建了一个可以在 lambda 表达式中引用的“有效最终”变量,因为该值是在执行时捕获的。arg
防止捕获“非有效最终”值或多或少只是Java中的一种预防措施,因为在其他语言(例如JavaScript)中,这的工作方式不同。
因此,编译器理论上可以转换您的代码,捕获该值并继续,但它必须以不同的方式存储该值,并且您可能会得到意外的结果。因此,为 Java 8 开发 lambda 的团队通过阻止这种情况(有异常)正确地排除了这种情况。
如果您需要更改 lambda 表达式中外部变量的值,则可以声明一个单元素数组:
String[] myStringRef = { "before" };
someCallingMethod(() -> myStringRef[0] = "after" );
System.out.println(myStringRef[0]);
或者用于使其线程安全。但是,对于您的示例,这可能会返回“before”,因为回调很可能在执行 println 之后执行。AtomicReference<T>