for-each循环中调用集合自身的remove()会抛concurrentmodificationexception,因fail-fast机制校验modcount不一致;安全删除需用iterator.remove()、removeif()或直接list.remove()(单个元素)。

for-each 循环里调用 remove() 会抛 ConcurrentModificationException
Java 的 ArrayList 和 LinkedList 这类集合在迭代时做了快速失败(fail-fast)校验。当你用 for-each(本质是隐式使用 Iterator)遍历时,任何直接调用集合自身的 remove() 方法都会让内部的 modCount 和迭代器记录的 expectedModCount 不一致,触发异常。
常见错误写法:
for (String s : list) {
if ("abc".equals(s)) {
list.remove(s); // ⚠️ 这里崩
}
}
- 不是“不能删”,而是“不能用集合自己的
remove()在 for-each 里删” -
for-each是语法糖,背后仍是Iterator,它只允许通过iterator.remove()安全删除当前元素 - 哪怕只删一个元素,只要在循环体里调了
list.remove(),就大概率出错(除非恰好删的是最后一个且迭代器还没走到下一轮)
安全删除的三种主流做法及适用场景
选哪种取决于你要删几个元素、是否需要保留原顺序、以及对性能的敏感度。
-
单个元素确定存在 → 用
list.remove(Object)直接删:
不涉及迭代,最简单。但注意:它只删第一个匹配项,返回boolean;如果要删所有匹配项,这不够用。 -
删多个匹配元素 → 用
Iterator.remove():
这是 for-each 的“正确打开方式”,但得显式写Iterator:
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if ("abc".equals(s)) {
it.remove(); // ✅ 唯一被允许的删除方式
}
}
-
Java 8+ 批量删 → 用
removeIf():
语义清晰、代码短,底层仍用Iterator,线程不安全但安全可靠:
list.removeIf(s -> "abc".equals(s));
-
removeIf()会遍历全部元素,无法中途跳出;若想删到第一个就停,还是得手写Iterator+break - 对
CopyOnWriteArrayList等特殊集合,removeIf()行为不同(它基于快照),一般业务不用这类集合,不必提前纠结
removeIf() 和手写 Iterator 的性能差异在哪
两者底层都是单次遍历 + Iterator.remove(),时间复杂度都是 O(n),差别在 JVM 层面和可读性上。
立即学习“Java免费学习笔记(深入)”;
-
removeIf()是 JDK 内置方法,在ArrayList中做了小优化:预估删除数量后,可能减少数组复制次数 - 手写
Iterator更灵活:比如你删完一个就break,或删的时候顺带收集被删元素,removeIf()做不到 - 不要为了“性能”硬选手写版——除非压测真卡在这里,否则
removeIf()更少出错、更易维护 - 注意:别在
removeIf()的 predicate 里修改集合本身(比如又调一次list.remove()),那会破坏迭代状态
容易被忽略的边界:null 元素和自定义对象的 equals
删元素靠的是 equals() 判断,不是 ==。这点一疏忽,删不掉还找不到原因。
- 如果列表里有
null,而你写"abc".equals(s),那s == null时自然跳过——这是对的;但如果你写s.equals("abc"),遇到null就NullPointerException - 删自定义对象时,必须重写
equals()和hashCode(),否则remove()或removeIf()都按引用比较,永远不匹配 -
list.remove(new Person("Alice"))能否成功,完全取决于Person类有没有正确实现equals();没重写就等于在比内存地址
实际编码中,这个点比“怎么删”更容易翻车——因为运行不报错,只是逻辑不对。










