使用forEach遍历ArrayList时仅更新2个元素的原因排查
问题分析:ArrayList.forEach修改元素值异常的原因
问题场景
创建一个初始全为0的ArrayList,后续所有元素变为1。尝试用forEach结合Lambda将所有元素改回0时,仅前2个元素被修改,最终结果为[0, 0, 1, 1, 1],代码如下:
// 创建大小为5、元素全为0的列表 List<Integer> freeFrame = new ArrayList<Integer>(Collections.nCopies(5, 0)); // 后续操作后,freeFrame变为 [1, 1, 1, 1, 1] // 尝试将所有元素改回0 if(!freeFrame.contains(0)) freeFrame.forEach((b) -> freeFrame.set(b, 0)); // 最终freeFrame = [0, 0, 1, 1, 1]
核心原因:Lambda参数是元素值而非索引
这是逻辑错误导致的结果,和增删元素触发的并发修改异常无关——forEach的Lambda参数b是列表中当前遍历到的元素值,不是元素的索引,你的修改逻辑完全偏离了预期。
具体执行过程拆解
初始状态:freeFrame = [1,1,1,1,1],满足!contains(0)的条件,进入forEach遍历:
- 第一次遍历:
b = 1(第一个元素的值),执行set(1, 0),列表变为[1,0,1,1,1] - 第二次遍历:
b = 0(第二个元素的值),执行set(0, 0),列表变为[0,0,1,1,1] - 第三次遍历:
b = 1(第三个元素的值),执行set(1, 0),但该位置已经是0,无变化 - 第四、五次遍历:
b都是1,重复执行set(1, 0),无任何实际修改
最终就得到了[0,0,1,1,1]的结果。
正确实现方式
方式1:普通索引循环(你已采用的方法)
if(!freeFrame.contains(0)) { for(int i = 0; i < freeFrame.size(); i++) { freeFrame.set(i, 0); } }
方式2:用replaceAll方法(更简洁)
List.replaceAll专门用于批量修改元素值,逻辑清晰且代码简洁:
if(!freeFrame.contains(0)) { freeFrame.replaceAll(x -> 0); }
方式3:结合索引的forEach(借助IntStream)
如果一定要用forEach风格且基于索引操作,可以用流处理生成索引范围:
if(!freeFrame.contains(0)) { IntStream.range(0, freeFrame.size()).forEach(i -> freeFrame.set(i, 0)); }
内容的提问来源于stack exchange,提问作者Chad Lanoway
相关产品推荐
相关产品推荐

