为了账号安全,请及时绑定邮箱和手机立即绑定

增强的“for”循环和 lambda 表达式

增强的“for”循环和 lambda 表达式

料青山看我应如是 2022-05-21 20:25:57
据我了解,lambda 表达式捕获的是值,而不是变量。例如,以下是编译时错误: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一切正常: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循环也在内部某处增加变量,如普通循环所做的那样。**
查看完整描述

3 回答

?
翻过高山走不出你

TA贡献1875条经验 获得超3个赞

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


for (int k = 0; k < 10; k++) {

    final int d = k

    new Thread(() -> System.out.println(d)).start();

}

实际上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);

    }

}

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


防止捕获“非有效最终”值或多或少只是 Java 中的一种预防措施,因为在其他语言(例如 JavaScript)中,这会有所不同。


所以编译器理论上可以翻译你的代码,捕获值,然后继续,但它必须以不同的方式存储那个值,你可能会得到意想不到的结果。因此,为 Java 8 开发 lambdas 的团队正确地排除了这种情况,通过异常阻止它。


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


String[] myStringRef = { "before" };

someCallingMethod(() -> myStringRef[0] = "after" );

System.out.println(myStringRef[0]);

或使用AtomicReference<T>使其成为线程安全的。但是,对于您的示例,这可能会返回“之前”,因为回调很可能在 println 执行之后执行。


查看完整回答
反对 回复 2022-05-21
?
慕工程0101907

TA贡献1887条经验 获得超5个赞

在增强的 for 循环中,每次迭代都会初始化变量。来自Java 语言规范(JLS)的§14.14.2 :


...


当执行增强for语句时,局部变量在循环的每次迭代中被初始化为数组的连续元素或Iterable由表达式产生。增强语句的确切含义for通过翻译成基本for语句给出,如下:


如果Expression的类型是 的子类型Iterable,则翻译如下。


如果Expression的类型是Iterable<X>某个类型参数的子类型X,则令I为类型java.util.Iterator<X>;否则,I设为原始类型java.util.Iterator。


增强for语句等价于for以下形式的基本语句:


for (I #i = Expression.iterator(); #i.hasNext(); ) {

    {VariableModifier} TargetType Identifier =

        (TargetType) #i.next();

    Statement

}

...


否则,表达式必须具有数组类型,T[]。


让L1 ... Lm是紧接在增强for语句之前的(可能为空的)标签序列。


增强for语句等价于for以下形式的基本语句:


T[] #a = Expression;

L1: L2: ... Lm:

for (int #i = 0; #i < #a.length; #i++) {

    {VariableModifier} TargetType Identifier = #a[#i];

    Statement

}

...


换句话说,您的增强 for 循环等效于:


ArrayList<Integer> listOfInt = new ArrayList<>();

// add elements...


for (Iterator<Integer> itr = listOfInt.iterator(); itr.hasNext(); ) {

    Integer arg = itr.next();

    new Thread(() -> System.out.println(arg)).start();

}

由于每次迭代都会初始化变量,因此它实际上是最终的(除非您在循环内修改变量)。


相反,基本 for 循环中的变量(k在您的情况下)被初始化一次并在每次迭代时更新(如果存在“ ForUpdate ”,例如k++)。有关详细信息,请参阅JLS 的§14.14.1。由于变量更新,每次迭代都不是最终的,也不是有效的最终。


JLS 的§15.27.2规定并解释了对最终或有效最终变量的需求:


...


任何使用但未在 lambda 表达式中声明的局部变量、形式参数或异常参数都必须声明final或有效地最终确定(第 4.12.4 节),否则在尝试使用时会发生编译时错误。


任何使用但未在 lambda 主体中声明的局部变量必须在 lambda 主体之前明确分配(第 16 节(Definite Assignment)),否则会发生编译时错误。


变量使用的类似规则适用于内部类的主体(第 8.1.3 节)。对有效最终变量的限制禁止访问动态变化的局部变量,这些变量的捕获可能会引入并发问题。与final限制相比,它减轻了程序员的文书负担。


对有效最终变量的限制包括标准循环变量,但不包括增强for循环变量,它们对于循环的每次迭代都被视为不同的(第 14.14.2 节)。


...


最后一句话甚至明确提到了基本 for 循环变量和增强型 for 循环变量之间的区别。


查看完整回答
反对 回复 2022-05-21
?
慕容森

TA贡献1853条经验 获得超18个赞

其他回复很有帮助,但他们似乎没有直接解决问题并明确回答。


在您的第一个示例中,您尝试k从 lambda 表达式进行访问。这里的问题是k随着时间的推移改变它的值(k++在每次循环迭代之后调用)。Lambda 表达式确实捕获了外部引用,但它们需要被标记为final或“有效地最终”(即,将它们标记为final仍会产生有效代码)。这是为了防止并发问题;在您创建的线程运行时,k可能已经拥有一个新值。


另一方面,在您的第二个示例中,您正在访问的变量是arg,它会在增强的 for 循环的每次迭代中重新初始化(与上面的示例相比,k仅更新),因此您正在创建一个完全每次迭代的新变量。顺便说一句,您还可以将增强型 for 循环的迭代变量显式声明为final:


for (final Integer arg : listOfInt) {

    new Thread(() -> System.out.println(arg)).start();

}

这可确保在arg您创建的线程运行时值引用不会更改。


查看完整回答
反对 回复 2022-05-21
  • 3 回答
  • 0 关注
  • 353 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信