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
相关产品推荐
相关产品推荐

