不能边遍历边用集合的remove()方法删除元素,因为会触发ConcurrentModificationException;这是迭代器“快速失败”机制所致,modCount与expectedModCount不匹配即抛异常。

为什么不能边遍历边用集合的remove()方法
直接调用 ArrayList.remove() 或 HashSet.remove() 删除正在遍历的元素,会触发 ConcurrentModificationException。这不是线程安全问题,而是迭代器的“快速失败”(fail-fast)机制在起作用:集合内部的 modCount 和迭代器记录的 expectedModCount 对不上,立刻抛异常。
常见错误现象:循环里写 list.remove(obj),运行到第二次迭代就崩;或者用增强 for 循环(本质是 Iterator),一样报错。
- 增强 for 循环底层就是
Iterator,它没提供删除入口,所以禁止删除 -
Collection.removeIf()是例外,它内部封装了安全删除逻辑,但属于批量操作,不适用于需要条件判断+单步控制的场景 - 手动维护索引倒序遍历(如
for (int i = list.size()-1; i >= 0; i--))能避开异常,但仅对List有效,且逻辑易错、可读性差
Iterator.remove() 是唯一合法的单步删除方式
Iterator.remove() 是设计上唯一被允许的、在遍历中途删除当前元素的方法。它会同步更新迭代器状态,修正 expectedModCount,从而绕过快速失败检查。
使用前提是:必须先调用 next(),拿到当前元素后才能调用 remove();否则会抛 IllegalStateException。
- 不能连续调两次
remove()(中间没调next()) - 不能在
next()前调用remove() - 每个
next()最多对应一次remove()
Iterator<string> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if (s.startsWith("a")) {
it.remove(); // ✅ 正确:next之后立即remove
}
}</string>
不同集合类型下 Iterator 的行为差异
Iterator 接口行为一致,但底层实现影响性能和语义细节:
-
ArrayList.iterator()返回的是内部类Itr,remove()是 O(n) 时间(数组元素前移) -
LinkedList.iterator()的remove()是 O(1),因为双向链表删节点快 -
HashSet.iterator()的remove()是 O(1) 平均复杂度,但哈希表 rehash 可能隐式发生 -
TreeSet.iterator()的remove()是 O(log n),红黑树结构调整开销固定
注意:ConcurrentHashMap 或 CopyOnWriteArrayList 的迭代器不抛 ConcurrentModificationException,但它们的 remove() 不是线程安全的删除操作——那是另一套并发模型,别混用。
容易忽略的边界情况和坑
实际写的时候,最容易栽在「空指针」和「并发修改检测失效」上:
- 如果集合本身是
null,调iterator()就 NPE,得先判空 - 用
Stream.iterator()(比如list.stream().filter(...).iterator())得到的不是集合原生迭代器,它的remove()默认抛UnsupportedOperationException - 多个线程共享同一个
Iterator实例(极少见但有人这么干),即使用了remove()也照样出问题——Iterator本身不是线程安全的 - 在 Lambda 表达式里捕获迭代器并异步调用
remove(),会导致状态错乱,因为Iterator是一次性、顺序驱动的状态机
真正安全的删除只发生在「同一迭代器实例、单线程、next 后紧跟 remove」这个窄通道里。超出这个范围,就得换思路:收集待删元素再批量删,或者用 removeIf()、Collectors.partitioningBy() 等函数式替代方案。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











