遍历集合时直接调用 list.remove() 会抛 concurrentmodificationexception,因迭代器通过 modcount 检测结构修改,单线程下亦触发;正确做法包括倒序遍历、iterator.remove() 或 removeif()。

为什么遍历集合时直接调用 list.remove() 会抛 ConcurrentModificationException
Java 的 ArrayList、LinkedList 等非线程安全集合,在迭代过程中检测到结构被意外修改(比如用 list.remove() 删除元素),就会立即抛出 ConcurrentModificationException。这不是并发导致的,哪怕单线程也会触发——因为迭代器内部维护了一个 modCount 计数器,每次集合结构变化(增删元素)都会更新它,而迭代器在 next() 前会校验该值是否匹配。
常见错误现象:
- 循环里写
for (String s : list) { if (s.isEmpty()) list.remove(s); }→ 必崩 - 用传统
for (int i = 0; i ,删完没调整 <code>i→ 漏删或越界
用 Iterator.remove() 是最稳妥的单线程删除方式
迭代器的 remove() 方法是唯一被设计为“边遍历边删”的合法入口。它会在删除后同步更新自身状态,避免 modCount 校验失败。
使用场景:需要根据元素内容条件过滤,且必须原地修改原集合(不能新建)。
立即学习“Java免费学习笔记(深入)”;
实操要点:
- 必须在
iterator.next()之后立刻调用iterator.remove();不能隔一轮或多轮再删 - 每个
next()最多对应一次remove();重复调用会抛IllegalStateException - 不支持在
forEach循环或 lambda 中使用(因为拿不到迭代器实例)
示例:
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if (s == null || s.trim().isEmpty()) {
it.remove(); // ✅ 安全
}
}
替代方案:用 removeIf() 更简洁(Java 8+)
Collection.removeIf() 内部封装了迭代器逻辑,语义清晰、代码短,且同样安全。它底层就是用 Iterator.remove() 实现的。
参数差异:
- 接收一个
Predicate<t></t>,返回true的元素会被删除 - 不能在 predicate 里修改集合本身(比如再调
removeIf),否则仍会抛异常
性能影响:和手动迭代器基本一致,没有额外拷贝开销;但要注意 predicate 不要带副作用或耗时操作。
示例:
list.removeIf(s -> s == null || s.trim().isEmpty()); // ✅ 推荐日常使用
哪些情况不能用 removeIf() 或迭代器?选对工具很关键
如果需要在删除的同时做复杂状态判断(比如依赖前一个元素、累计计数、跨集合查表),或者要保留被删元素做后续处理,那 removeIf() 就不够用了。
此时更合适的做法是:
- 先收集待删索引或引用(如
List<integer> toRemoveIndices</integer>),再倒序遍历删除(避免索引偏移) - 用
Stream.filter()生成新集合(注意:不修改原集合,内存有开销) - 用
CopyOnWriteArrayList(仅适用于读多写少、允许写时复制的并发场景;普通场景反而拖慢性能)
容易踩的坑:
- 误以为
CopyOnWriteArrayList能解决所有遍历时删除问题——它只是不抛异常,但迭代的是快照,删完原集合可能已不是你预期的样子 - 在增强 for 循环里用
list.remove()报错后,改成for (int i = list.size()-1; i >= 0; i--)却忘了加i--控制步进,导致死循环
真正难的从来不是语法,而是想清楚:这个删除动作,到底需不需要实时反映在当前迭代中?要不要复用原集合?有没有并发需求?选错路径,后面全是补丁。









