You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Groovy中使用Future时for循环与each循环的行为不一致问题

Groovy中for循环与each循环在闭包捕获变量时的差异问题

现象描述

遍历对象列表并提交Callable到线程池时,使用Groovy原生的each循环能得到每个对象的正确属性值,而使用Java风格增强for循环时,所有Callable都返回最后一个对象的属性值。该问题在Groovy2、Groovy3、Groovy4版本中均存在。

代码示例

package tmp

import java.util.concurrent.*

class TestCode {

    public static final ExecutorService executorService = Executors.newFixedThreadPool(10)

    static void main(String[] args) {
        normal()
        abnormal()
        System.exit(0)

    }

    static void normal(){
        SomeInfo info1 = new SomeInfo(on: '001')
        SomeInfo info2 = new SomeInfo(on: '002')
        List<SomeInfo> infos = [info1,info2]

        List<Future<String>> futureList = new ArrayList<>()
        infos.each {SomeInfo info ->
            Future<String> future = executorService.submit(new Callable<String>() {
                @Override
                String call() throws Exception {
                    return info.getOn()
                }
            })
            futureList.add(future)
        }
        futureList.each {
            def get = it.get()
            println "normal get="+get
        }
    }

    static void abnormal(){
        SomeInfo info1 = new SomeInfo(on: '001')
        SomeInfo info2 = new SomeInfo(on: '002')
        List<SomeInfo> infos = [info1,info2]

        List<Future<String>> futureList = []
        for(SomeInfo info:infos){
            Future<String> future = executorService.submit(new Callable<String>() {
                @Override
                String call() throws Exception {
                    return info.getOn()
                }
            })
            futureList.add(future)
        }
        futureList.each {
            def get = it.get()
            println "abnormal get="+get
        }
    }

    private static class SomeInfo{
        private String on

        String getOn() {
            return on
        }

        void setOn(String on) {
            this.on = on
        }
    }
}

输出结果

normal get=001
normal get=002
abnormal get=002
abnormal get=002

原因分析

核心差异在于两种循环对循环变量的作用域处理逻辑不同:

  • each循环:Groovy的each是Iterable的扩展方法,每次迭代都会创建一个独立的作用域,当前迭代的info变量是该作用域内的局部变量。每个Callable闭包捕获的是当前迭代步的info变量引用,彼此独立,不会被后续迭代覆盖。
  • Java风格增强for循环:Groovy编译这种循环时,会在循环体外定义一个单一的info变量,每次迭代仅更新该变量的引用指向列表中的下一个元素。Callable闭包捕获的是这个变量本身的引用,而非迭代时的元素值。当线程池中的Callable执行时,循环已完成,info最终指向列表的最后一个元素,因此所有Callable都返回最后一个元素的属性值。

本质上是闭包捕获的是变量的引用而非当前值,两种循环的变量作用域设计导致了结果差异。

解决方法

在for循环中,为每次迭代创建一个新的局部变量,让闭包捕获这个局部变量的引用,避免共用同一个循环变量:

static void fixedAbnormal(){
    SomeInfo info1 = new SomeInfo(on: '001')
    SomeInfo info2 = new SomeInfo(on: '002')
    List<SomeInfo> infos = [info1,info2]

    List<Future<String>> futureList = []
    for(SomeInfo info:infos){
        SomeInfo localInfo = info // 创建局部变量,每个迭代拥有独立作用域
        Future<String> future = executorService.submit(new Callable<String>() {
            @Override
            String call() throws Exception {
                return localInfo.getOn()
            }
        })
        futureList.add(future)
    }
    futureList.each {
        def get = it.get()
        println "fixed abnormal get="+get
    }
}

修改后输出会与normal方法一致,得到正确的每个对象属性值。

内容的提问来源于stack exchange,提问作者maojf

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 00:47:54