Java集合遍历时直接调用remove()会抛ConcurrentModificationException,因迭代器采用fail-fast机制,通过modCount校验结构修改;正确方式是使用Iterator.remove()同步更新预期值。

为什么在Java集合遍历时直接调用 remove() 会抛 ConcurrentModificationException
因为大多数Java集合(如 ArrayList、HashMap、HashSet)的迭代器是“快速失败”(fail-fast)的。它们内部维护一个 modCount 计数器,记录结构修改次数;每次调用 iterator.next() 前都会校验当前 modCount 是否与迭代器创建时保存的 expectedModCount 一致。一旦你用集合自身的 remove() 删除元素,modCount 就变了,但迭代器并不知情,下一次 next() 就触发校验失败,抛出异常。
正确删除方式:必须用 Iterator.remove()
这是唯一被迭代器允许的、安全的删除操作。它会在删除后同步更新 expectedModCount,避免校验失败。
- 不能用
for-each循环删除(底层就是Iterator,但不暴露remove()方法) - 不能用普通
for (int i = 0; i 并在循环中调用list.remove(i)—— 会导致漏删或越界(索引偏移) - 必须显式获取迭代器,并在
next()之后、下一次next()之前调用其remove()
Listlist = new ArrayList<>(Arrays.asList("a", "b", "c", "b")); Iterator it = list.iterator(); while (it.hasNext()) { String s = it.next(); if ("b".equals(s)) { it.remove(); // ✅ 正确:调用迭代器自己的 remove() } } // list 现在是 ["a", "c"]
其他可行方案及其适用场景
不是所有情况都适合用 Iterator.remove()。比如需要根据复杂条件批量删除、或需兼容不可变集合时,可选以下方式:
-
removeIf(Predicate)(Java 8+):简洁安全,内部仍用迭代器,但封装了逻辑list.removeIf(s -> s.startsWith("a")); - 倒序
for循环(仅限List):从后往前删,避免索引偏移for (int i = list.size() - 1; i >= 0; i--) { if (condition) list.remove(i); } - 收集待删元素,遍历结束后统一删:
list.removeAll(toRemove);注意内存开销和两次遍历成本 - 并发场景必须换集合:如
CopyOnWriteArrayList或ConcurrentHashMap,但性能代价大,别滥用
容易忽略的坑:增强 for 循环 + 异常处理 + 多线程
很多人以为加个 try-catch 就能绕过 ConcurrentModificationException,其实只是掩盖问题,逻辑已错乱。更隐蔽的是多线程场景:即使单线程没报错,多个线程共用同一个非线程安全集合并一边读一边删,依然可能崩溃或数据丢失。
立即学习“Java免费学习笔记(深入)”;
- 增强
for循环本质是语法糖,等价于隐式Iterator,一样会抛异常 -
ConcurrentModificationException不代表一定有“并发”,单线程误删也会触发 -
Vector和Stack虽然方法加了synchronized,但其迭代器仍是 fail-fast 的,同样会抛该异常 - 使用
Stream.filter().collect()是安全的,但生成的是新集合,原集合不变
真正要解决的从来不是“怎么 catch 这个异常”,而是“怎么避免触发它”。核心就一条:遍历时,只通过当前迭代器的 remove() 改集合结构。










