集合迭代失效是Java为保障遍历一致性设计的主动防护机制,通过modCount版本校验触发ConcurrentModificationException,提示遍历与修改未协调;安全删除需用迭代器remove()、removeIf()、倒序for或先收集后批量删;CopyOnWriteArrayList仅适用于读多写少场景。

理解集合迭代失效,关键不是记住“不能在遍历中删”,而是看清它背后是一套主动防护的契约机制——不是 bug,是设计。
失效不是崩溃,是契约被打破的明确警告
Java 的迭代器默认是“只读观察者”。它创建时拍下集合当前的结构版本号(modCount),存在自己身上(expectedModCount)。之后每次调 next() 前,都核对一次:版本号没变,才继续;变了,立刻抛 ConcurrentModificationException。
这个异常不表示程序出错了,而是在说:“你一边看清单,一边偷偷改清单内容——我没法保证接下来看到的是什么,停!” 即使单线程里写 for (String s : list) { list.remove(s); },也一样触发,因为它违反了“遍历与修改必须由同一抽象层协调”的约定。
哪些操作会悄悄破坏版本一致性
- 增强 for 循环里调用
list.remove():底层仍是迭代器,但删除动作来自集合自身,expectedModCount没更新 - 普通 for 正向遍历时用
list.remove(i):虽不抛异常,但元素前移导致漏删(比如删掉索引 1 的元素后,原索引 2 的元素移到 1,而循环已执行i++,直接跳过) - 多个迭代器共用一个集合,其中一个调了
remove():其他迭代器下次next()也会因版本不匹配而失败
真正安全的删除方式
-
用迭代器自己的
remove():它在删的同时同步更新expectedModCount,保持校验通过。注意必须紧跟在next()之后,且每 call 一次next()最多 call 一次remove() -
用
removeIf()(Java 8+):一行代码搞定,语义清晰,底层封装了标准迭代器逻辑,自动规避校验失败 -
倒序 for 循环(仅 List):从
size-1往 0 遍历,删后面元素不影响前面索引,完全绕开迭代器校验 -
先收集再批量删:遍历存入临时集合
toRemove,再调list.removeAll(toRemove),两次遍历但职责分离,逻辑干净
别迷信 CopyOnWriteArrayList
它确实不抛 ConcurrentModificationException,因为它的迭代器基于不可变快照——你删完,别人还在读旧数组。但这带来三个现实问题:
立即学习“Java免费学习笔记(深入)”;
- 迭代中看不到新添加的元素
- 已删除的元素仍可能被遍历出来
- 每次写操作都要复制整个数组,内存和 CPU 开销大
它只适合读远多于写的场景,比如监听器列表、配置项缓存。拿它当通用替代方案,容易在数据实时性或性能上翻车。


















