Java集合迭代失效源于fail-fast机制,通过modCount与expectedModCount校验实现;直接调用集合remove会触发ConcurrentModificationException,安全删除需用Iterator.remove()、removeIf()、倒序遍历或批量删除。

Java 中集合迭代失效的核心在于 fail-fast(快速失败)机制,不是线程安全问题,而是单线程下为保障数据一致性而设的主动防护。它通过两个关键字段协同工作:集合内部的 modCount(结构修改计数器),和迭代器创建时保存的 expectedModCount(预期值)。每次调用 iterator.next() 前,都会检查二者是否一致;一旦不等,立刻抛出 ConcurrentModificationException。
为什么直接调用集合的 remove 会触发失效
当你在 foreach 或 while + iterator 遍历中执行 list.remove(obj) 时:
-
list.remove()内部会修改集合结构,使modCount++ - 但迭代器并不知情,
expectedModCount仍维持旧值 - 下一次
next()调用立即触发校验失败,抛异常
这本质上是设计使然——迭代器视自己为“只读观察者”,任何非它发起的结构变更都被视为不一致风险。
常见错误写法及失效表现
以下操作看似合理,实则都绕不过 fail-fast 检查:
立即学习“Java免费学习笔记(深入)”;
-
增强 for 循环中调用
list.remove():底层仍是迭代器,第二次 next 就崩 -
普通 for 循环正向遍历时用
list.remove(i):虽不抛异常,但因元素前移导致漏删(如连续两个 "bb",删掉第一个后第二个移到原位置 i,循环 i++ 就跳过了) - 多个迭代器共用一个集合,其中一个调了 remove:其他迭代器下次 next 同样报错
真正安全的删除方式
绕过失效的关键是让“修改”与“迭代”由同一控制主体完成:
-
用
Iterator.remove():它在删除同时同步更新expectedModCount,保持校验通过 -
用
Collection.removeIf()(Java 8+):一行代码,底层封装了标准迭代器逻辑,语义清晰且无误 -
倒序 for 循环(仅 List):从
size-1往 0 遍历,删后面元素不影响前面索引,不依赖迭代器 -
收集后批量删:先遍历存入临时集合
toRemove,再调list.removeAll(toRemove),两次遍历但逻辑隔离
注意边界情况
这些细节常被忽略,却直接影响行为:
-
Iterator.remove()必须紧跟在next()之后,且每 call 一次next()最多只能 call 一次remove(),否则抛IllegalStateException -
removeIf的 lambda 体内不能再调集合的增删方法,否则仍触发异常 -
CopyOnWriteArrayList可规避异常,但迭代器看到的是快照,删完别人看不到,且写操作开销大,仅适合读远多于写的场景


















