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

查看:406
本文介绍了增强的"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();
}

实际上,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循环(在非数组上)创建一个隐藏的Iterator.您可以通过以下方式对此进行测试:

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的lambda的团队通过例外地阻止了这种情况,从而正确地排除了这种情况.

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<T>使其具有线程安全性.但是,在您的示例中,由于回调很可能在println执行之后执行,因此这可能会返回"before".

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天全站免登陆