java中foreach循环遍历时直接修改集合会触发concurrentmodificationexception;安全删除方式包括:①用iterator.remove();②倒序for循环索引删除;③收集待删元素后批量处理;④真并发场景才用copyonwritearraylist等并发集合。

在 Java 中,foreach 循环(增强 for 循环)底层使用 Iterator 遍历集合,而 Iterator 的设计原则是“遍历时不可直接修改集合结构”。如果在 foreach 中调用 list.remove()、list.add() 或 map.put() 等会改变集合大小或结构的操作,就会触发 ConcurrentModificationException —— 即使是单线程环境也会抛出,因为这是 fail-fast 机制的主动检查,不是真正的并发冲突。
用迭代器的 remove() 安全删除
这是最常用、最推荐的方式:获取 Iterator 后,只通过 iterator.remove() 删除当前元素。
- 该方法是 Iterator 唯一允许的修改操作,会同步更新内部
modCount和expectedModCount - 只能删除刚 next() 出来的那个元素,不能重复调用,也不能用于添加
- 示例:
Iterator
while (it.hasNext()) {
String s = it.next();
if ("b".equals(s)) it.remove(); // ✅ 安全
} // 结果:["a", "c"]
倒序 for 循环 + 索引删除(仅适用于 List)
对 ArrayList、LinkedList 等支持随机访问或尾删高效的 List,可用传统 for 循环从后往前遍历并用索引删除。
- 倒序可避免因前面元素删除导致后续索引偏移,无需调整 i
- 注意:不要用正序循环中
remove(i),否则会跳过下一个元素 - 示例:
if ("b".equals(list.get(i))) {
list.remove(i); // ✅ 安全(索引不会错位)
}
}
收集待删元素,遍历结束后批量处理
适合需要复杂判断、或需同时增删多个位置的场景。先用另一个集合暂存要删除的对象(或索引),再统一操作。
- 适用于任意 Collection,包括 Set、Map 的 keySet/entrySet
- 注意:对 ArrayList,批量调用
removeAll()比逐个 remove 更高效;对 LinkedList 则未必 - Map 中如需删除 entry,建议收集 key 后调用
map.keySet().removeAll(keysToRemove) - 示例:
for (String s : list) {
if (s.length() == 1) toRemove.add(s);
}
list.removeAll(toRemove); // ✅ 安全且语义清晰
使用并发集合(仅当真需并发修改时)
如果业务确实要求多线程边遍历边修改,应换用线程安全的集合类,例如:
-
CopyOnWriteArrayList:读多写少,遍历时写操作不干扰迭代器(但内存和性能开销大) -
ConcurrentHashMap:支持安全地边遍历边 put/remove,其迭代器弱一致性,不抛 CME - ⚠️ 注意:这些不是 foreach 的“补救方案”,而是架构层面的选择——单线程下没必要用,反而降低性能
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











