增强的“for"循环和 lambda 表达式 [英] Enhanced 'for' loop and lambda expressions

查看:23
本文介绍了增强的“for"循环和 lambda 表达式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

据我所知,lambda 表达式捕获的是值,而不是变量.例如,以下是编译时错误:

To my understanding, lambda expressions capture values, not the variables. For example, the following is a compile-time error:

for (int k = 0; k < 10; k++) {
    new Thread(() -> System.out.println(k)).start();
    // Error—cannot capture k
    // Local variable k defined in an enclosing scope must be final or effectively final
   }

但是,当我尝试使用增强的 for-loop 运行相同的逻辑时,一切正常:

However when I try to run the same logic with enhanced for-loop everything is working fine:

List<Integer> listOfInt = new Arrays.asList(1, 2, 3);

for (Integer arg : listOfInt) {
    new Thread(() -> System.out.println(arg)).start();
    // OK to capture 'arg'
 }

为什么它适用于增强的 for 循环而不适用于普通的传统 for 循环,尽管增强的 for 循环也是像普通循环一样在内部某处增加变量.**

Why is it working fine for an enhanced for loop and not for a normal conventional for loop, although the enhanced for loop is also somewhere inside incrementing the variable as done by a normal loop.**

推荐答案

Lambda 表达式的工作方式类似于回调.在代码中传递它们的那一刻,它们存储"了操作所需的任何外部值(或引用)(就像这些值在函数调用中作为参数传递一样.这只是对开发人员隐藏了).在您的第一个示例中,您可以通过将 k 存储到一个单独的变量来解决这个问题,例如 d:

Lambda expressions work like callbacks. The moment they are passed in the code, they 'store' any external values (or references) they require to operate (as if these values were passed as arguments in a function call. This is just hidden from the developer). In your first example, you could work around the problem by storing k to a separate variable, like d:

for (int k = 0; k < 10; k++) {
    final int d = k
    new Thread(() -> System.out.println(d)).start();
}

Effectively final 意味着,在上面的例子中,你可以省略 'final' 关键字,因为 d 实际上是最终的,因为它在其内部永远不会改变范围.

Effectively final means, that in the above example, you can leave the 'final' keyword out, because d is effectively final, since it is never changed within its scope.

for 循环的操作方式不同.它们是迭代代码(与回调相反).它们在各自的范围内工作,并且可以在自己的堆栈中使用所有变量.这意味着,for 循环的代码块是外部代码块的一部分.

for loops operate differently. They are iterative code (as opposed to a callback). They work within their respective scope and can use all variables on their own stack. This means, that the for loop's code block is part of the external code block.

至于您强调的​​问题:

As to your highlighted question:

增强的 for 循环不使用常规索引计数器操作,至少不是直接操作.增强的 for 循环(在非数组上)创建一个隐藏的迭代器.您可以通过以下方式进行测试:

An enhanced for loop does not operate with a regular index-counter, at least not directly. Enhanced for loops (over non-arrays) create a hidden Iterator. You can test this the following way:

Collection<String> mySet = new HashSet<>();
mySet.addAll(Arrays.asList("A", "B", "C"));
for (String myString : mySet) {
    if (myString.equals("B")) {
        mySet.remove(myString);
    }
}

上面的例子会导致 ConcurrentModificationException.这是因为迭代器注意到底层集合在执行过程中发生了变化.但是,在您的示例中,外部循环创建了一个有效最终"变量 arg 可以在 lambda 表达式中引用,因为该值是在执行时捕获的.

The above example will cause a ConcurrentModificationException. This is due to the iterator noticing that the underlying collection has changed during the execution. However in your very example, the external loop creates an 'effectively final' variable arg which can be referenced within the lambda expression, because the value is captured at execution time.

防止捕获非有效最终"值或多或少只是 Java 中的一种预防措施,因为在其他语言(例如 JavaScript)中,这有不同的工作方式.

The prevention of the capture of 'non-effectively-final' values is more or less just a precaution in Java, because in other languages (like JavaScript e.g.) this works differently.

因此,编译器理论上可以翻译您的代码、捕获值并继续,但它必须以不同的方式存储该值,并且您可能会得到意想不到的结果.因此,为 Java 8 开发 lambdas 的团队正确地排除了这种情况,通过例外阻止它.

So the compiler could theoretically translate your code, capture the value, and continue, but it would have to store that value differently, and you would probably get unexpected results. Therefore the team developing lambdas for Java 8 correctly excluded this scenario, by preventing it with an exception.

如果您需要更改 lambda 表达式中外部变量的值,您可以声明一个单元素数组:

If you ever need to change values of external variables within lambda expressions, you can either declare a one-element array:

String[] myStringRef = { "before" };
someCallingMethod(() -> myStringRef[0] = "after" );
System.out.println(myStringRef[0]);

或者使用 AtomicReference 使其成为线程安全的.但是,对于您的示例,这可能会返回before",因为回调很可能会在执行 println 之后执行.

Or use AtomicReference<T> to make it thread-safe. However with your example, this would probably return "before" since the callback would most likely be executed after the execution of println.

这篇关于增强的“for"循环和 lambda 表达式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆