如何禁用Groovy闭包惰性求值 解决循环变量捕获错误问题
问题原因
该输出异常的核心是Groovy闭包对for循环变量采用引用捕获而非值捕获:for循环中的ip是整个循环周期共用的同一个变量引用,循环执行完成后,该引用最终指向列表最后一个元素192.168.1.3。后续执行闭包时,所有闭包读取到的都是这个最终值,因此会出现三行完全相同的输出。
Groovy没有提供全局“禁用闭包惰性求值”的开关,只需要调整代码逻辑,让闭包捕获每次迭代独立、不会被后续循环修改的值即可得到预期结果,以下是三种可直接复用的调整方案:
解决方案
方案1:循环内定义局部变量拷贝当前迭代值
在for循环内部定义局部变量保存当前迭代的ip值,局部变量作用域仅限单次迭代,不会被后续循环逻辑修改,闭包捕获该局部变量即可拿到对应的值:
def ip_list = ['192.168.1.1', '192.168.1.2', '192.168.1.3' ] def task = [:] for (ip in ip_list) { // 单次迭代范围内的局部常量,存储当前ip值 def currentIp = ip task[ip] = { println "test on ${currentIp}" } } for (f in task.values()) { f() }
方案2:使用Groovy内置迭代方法替代原生for循环
Groovy自带的each、collect等集合迭代方法的入参是每次迭代独立的局部变量,不存在跨迭代的引用复用问题,写法也更符合Groovy的编码习惯:
def ip_list = ['192.168.1.1', '192.168.1.2', '192.168.1.3' ] def task = [:] ip_list.each { ip -> task[ip] = { println "test on ${ip}" } } task.values().each { it() }
方案3:使用闭包柯里化在创建时绑定当前值
通过curry方法在闭包创建阶段就把当前ip值作为固定参数绑定到闭包,后续执行闭包时会直接使用绑定时传入的值,不会受循环变量后续变化的影响:
def ip_list = ['192.168.1.1', '192.168.1.2', '192.168.1.3' ] def task = [:] for (ip in ip_list) { // 柯里化绑定当前ip为闭包的固定入参 task[ip] = { targetIp -> println "test on ${targetIp}" }.curry(ip) } for (f in task.values()) { f() }
三种方案运行后均会输出预期结果:
test on 192.168.1.1 test on 192.168.1.2 test on 192.168.1.3
内容的提问来源于stack exchange,提问作者Mr.Bingo
相关产品推荐
相关产品推荐

