直接在 foreach 循环中调用 list.remove() 会触发 concurrentmodificationexception 并导致跳过元素或逻辑错乱,因其破坏迭代器的 modcount 与 expectedmodcount 一致性;正确做法是使用迭代器自身的 remove() 方法或倒序 for 循环。

因为这会直接破坏迭代器的内部一致性,导致 ConcurrentModificationException(并发修改异常)——哪怕单线程环境下也会触发。
底层机制:迭代器在“盯梢”,而 remove 悄悄改了现场
foreach 循环本质是语法糖,编译后等价于显式使用 Iterator:
- 创建迭代器时,它会把集合当前的修改计数
modCount复制为自己的expectedModCount; - 每次调用
next()或hasNext()前,都会执行checkForComodification(),比对两者是否相等; - 一旦你在循环体中调用
list.remove(),modCount立即 +1,但expectedModCount仍保持原值; - 下一次迭代调用
next()时,校验失败,立刻抛出异常。
不止报错:还可能跳过元素或逻辑错乱
即使某些情况下没立即抛异常(比如删最后一个元素),行为也已不可靠:
- ArrayList 删除中间元素后,后续元素前移,但迭代器游标仍按原索引推进 → 下一个元素被跳过;
- 若循环中含 continue、嵌套判断或吞掉异常,可能让同个元素反复进入循环体 → 表现像“假死循环”;
- HashMap 等结构更敏感,甚至可能因哈希桶重排导致遍历提前终止或无限循环。
大厂为什么“严禁”而非“建议避免”
这不是风格偏好,而是稳定性和可维护性的硬性门槛:
- 确定性差:同一段代码,在不同 JDK 版本、不同集合实现、不同删除位置下,可能有时报错、有时跳数据、有时看似正常——极难复现和排查;
- 隐蔽性强:测试用例若恰好只删末尾元素,可能长期不暴露问题,上线后在特定数据分布下突然崩溃;
- 违反 fail-fast 设计契约:Java 集合明确用此机制提示开发者“你正在做危险操作”,绕过它等于主动放弃安全护栏。
正确做法就两条,且必须选其一
✓ 使用迭代器自身的 remove() 方法 —— 它会在删除后同步更新 expectedModCount,全程受控:
Iterator<string> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if (shouldRemove(s)) it.remove(); // 安全,仅删当前元素
}</string>
✓ 倒序 for 循环 —— 利用索引操作,避开迭代器机制:
for (int i = list.size() - 1; i >= 0; i--) {
if (shouldRemove(list.get(i))) list.remove(i);
}











