增强for循环禁止结构性修改集合,因其底层iterator通过modcount与expectedmodcount校验实现fail-fast机制;内容修改(如set、元素内部状态变更)则允许。

增强 for 循环本身不禁止你写修改集合的代码,但它底层依赖迭代器,而迭代器在设计上不允许遍历时直接调用 add()、remove() 等结构性修改方法——一旦这么做了,就会触发 ConcurrentModificationException。这不是并发导致的错误,而是 Java 集合的快速失败(fail-fast)机制主动抛出的异常,目的是防止遍历逻辑错乱。
底层是迭代器,且有状态校验
增强 for 循环编译后等价于手动获取并使用 Iterator:
- 每次调用
next()前,迭代器都会检查expectedModCount == modCount -
modCount是集合内部记录结构性修改次数的计数器,每次add、remove、clear等操作都会使其自增 - 但迭代器创建后,
expectedModCount就固定了;你在循环体里调用list.remove(),只改了modCount,没同步更新expectedModCount - 下一次
next()或hasNext()就会发现不一致,立刻抛异常
结构性修改 vs 内容修改,区别很关键
“不能修改集合结构”不等于“不能动集合里的东西”,这两类操作完全不同:
- 结构性修改:改变集合大小或组织方式,如
list.add(x)、list.remove(x)、map.put(k, v)—— 这些会触发异常 - 内容修改:不改变集合本身结构,只改元素内部状态,如
user.setName("A")、sb.append("x")—— 这不会触发异常,也确实生效 - 注意:
list.set(i, x)属于内容更新,不是结构性修改,所以安全,但增强 for 无法直接访问索引,得换写法
为什么不让它悄悄改?因为结果不可预测
即使去掉校验机制,边遍历边结构性修改依然危险:
- 在
ArrayList中删除元素可能导致后续元素被跳过(下标偏移) - 在
HashMap中插入新键值对可能触发 rehash,使迭代器指向无效桶位 - 某些集合(如
LinkedList)甚至可能出现无限循环或NullPointerException - 异常不是 bug,而是设计上的“提前拦截”,帮你避免更隐蔽的数据不一致问题
真正想改结构,得换安全方式
绕过增强 for,选择明确支持边遍历边修改的机制:
- 删元素 → 用
Iterator.remove()(唯一被迭代器允许的删除方式) - 批量删 → 先收集待删项,循环结束后调用
list.removeAll(toRemove) - List 场景 → 倒序普通 for 循环:
for (int i = list.size()-1; i >= 0; i--),删时不干扰前面索引 - 更新全部元素 → 直接用
list.replaceAll(unaryOperator)(JDK 8+,语义清晰又安全)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











