1. Java 集合遍历报错的常见原因
在 Java 编程中,集合遍历报错最常见的触发点往往发生在遍历过程中修改集合结构时,导致 Java 的 fail-fast 机制抛出异常。此类问题的核心通常指向对集合在迭代时进行增删改的行为,尤其是在使用增强 for 循环时。ConcurrentModificationException是最常见的错误类型之一,意味着迭代器发现底层集合在遍历过程中被修改。
另外一个常见原因是多线程环境下未正确同步对同一个集合进行并发修改,导致遍历期间的数据状态不一致,进而在迭代器内部抛出异常或产生不可预测的结果。此类情况常见于对 ArrayList、HashMap 等非线程安全的集合进行并发写操作。
还有一种情况是直接在遍历的循环体内对集合进行结构修改,如调用 remove、add 等方法,这会打破迭代器的预期快照,进而导致遍历报错或逻辑错位。在 for-each 语句中修改底层集合结构,是导致 Java 集合遍历报错的典型场景。
import java.util.*;
public class CMEExample {public static void main(String[] args) {List<String> list = new ArrayList<>(Arrays.asList("a","b","c","d"));// 错误演示:在遍历时修改集合for (String s : list) {if ("c".equals(s)) {list.remove(s); // 将在迭代过程中修改集合结构}}}
}
2. 常见错误的根本原因与排查步骤
2.1 fail-fast 机制与 modCount
Java 集合的迭代器通常实现了fail-fast行为,当检测到在迭代过程中对底层集合进行了结构性修改,迭代器就会通过抛出 ConcurrentModificationException 来暴露问题。此时需要关注集合的内部计数器 modCount 是否在遍历时被修改,导致迭代器认为集合状态已经改变。
排查时,第一步要观察堆栈信息中是否包含 ConcurrentModificationException,以及异常发生的具体代码位置。随后逐步定位所有在遍历语句附近对集合进行 add/remove 的操作,尤其是在 for-each、for 循环、Stream forEach 等语法路径中的修改。
2.2 如何定位并避免并发修改
如果运行环境存在多线程对同一个集合的并发修改,需要关注是否存在非线程安全集合的并发写操作。此时应该评估是否需要对集合进行同步、使用线程安全的实现,或采用分段锁等方案。错误往往出现在没有对共享集合进行正确的同步处理时。

排查步骤包括:查看涉及的集合类型、确认是否存在跨线程的写操作、以及检查遍历代码段是否包含异步任务对同一集合进行修改。通过将并发修改简化为单线程情景来重现问题,可以更清晰地定位原因。
2.3 常见的误用场景与诊断要点
另外一种常见场景是在增强 for 循环、Stream forEach、或显式迭代器遍历中直接修改集合,导致迭代器检测到修改。诊断要点包括:是否在迭代器外部执行修改、修改是否影响到同一个集合的结构、以及是否使用了不受保护的原始集合引用。
import java.util.*;
public class Diagnose {public static void main(String[] args) {List<String> list = new ArrayList<>(Arrays.asList("a","b","c","d"));// 诊断示例:在增强 for 循环内修改集合for (String s : list) {if ("c".equals(s)) {list.add("e"); // 可能触发 CME(结构修改)}}}
}
3. 从排查到修复的具体步骤
3.1 诊断步骤与排错清单
在遇到 Java 集合遍历报错时,第一时间应建立一个清晰的诊断清单:定位异常类型、确认是否为 ConcurrentModificationException、查找遍历代码中的修改操作、以及分析是否存在多线程并发修改的情况。接着逐步隔离代码段,将问题限定在单线程的最小可重现案例上。
其次,检查集合类型与遍历方式之间的契合度:ArrayList、LinkedList 等非线程安全集合在并发场景下往往需要额外的同步保护;而 CopyOnWriteArrayList 在遍历时对写操作提供了更好的容错性,但成本较高,适用于读多写少的场景。
3.2 重现与验证修复的思路
为验证修复效果,应能在一个可控的最小场景中重复触发前面的异常,并在修复后不再抛出异常。此过程帮助确认是否真的消除了并发修改、结构修改或迭代器相关的问题。
import java.util.*;
public class ReproduceBug {public static void main(String[] args) {List<String> list = new ArrayList<>(Arrays.asList("a","b","c","d"));// 触发 CME 的场景演示for (String s : list) {if ("c".equals(s)) list.remove(s); // 这会在遍历时修改集合}}
}
4. 常见修复案例(含代码示例)
4.1 使用 Iterator.remove() 进行就地删除
在遍历时希望删除元素,推荐的做法是使用 Iterator 自带的 remove() 方法,这样可以保持迭代器状态的一致性,避免抛出 ConcurrentModificationException。下面的示例展示了正确的写法。
关键点:在遍历过程中对集合进行删除时,使用 Iterator 的 remove,而不是直接对集合调用 remove。
import java.util.*;
public class IteratorRemove {public static void main(String[] args) {List<String> list = new ArrayList<>(Arrays.asList("a","b","c","d"));Iterator<String> it = list.iterator();while (it.hasNext()) {String s = it.next();if ("c".equals(s)) {it.remove(); // 安全删除当前迭代的元素}}System.out.println(list); // [a, b, d]}
}
4.2 使用 ListIterator 实现就地修改与替换
当需要在遍历过程中对元素进行修改(如替换、更新)时,ListIterator 提供了更丰富的接口,能安全地进行 set、add、remove 等操作。通过 ListIterator 可以在遍历过程中对序列进行就地修改且保持迭代器状态的正确性。
import java.util.*;
public class ListIteratorModify {public static void main(String[] args) {List<String> list = new ArrayList<>(Arrays.asList("a","b","c","d"));ListIterator<String> lit = list.listIterator();while (lit.hasNext()) {String s = lit.next();if ("b".equals(s)) {lit.set("beta"); // 修改当前元素}if ("d".equals(s)) {lit.remove(); // 删除当前元素}}System.out.println(list); // [a, beta, c]}
}
4.3 使用副本集合进行遍历以避免影响原集合
在某些场景下,允许对原集合进行修改,但为了避免细粒度修改带来的复杂性,可以先对集合创建一个快照进行遍历,再对原集合进行修改。此法的代价是额外创建一个副本对象,适用于读多写少的场景。
import java.util.*;
public class SnapshotTraversal {public static void main(String[] args) {List<String> list = new ArrayList<>(Arrays.asList("a","b","c","d"));// 先创建快照再遍历for (String s : new ArrayList<>(list)) {if ("c".equals(s)) list.remove(s);}System.out.println(list); // [a, b, d]}
}
4.4 使用 CopyOnWriteArrayList 提升并发遍历的安全性
CopyOnWriteArrayList 在遍历时能够承受并发写操作,因为写操作会产生一个快照,遍历仍然基于旧的快照进行。这种实现适用于“读多写少”的并发场景,可以避免 ConcurrentModificationException,但伴随的是写入时成本较高。
import java.util.*;
public class CopyOnWriteExample {public static void main(String[] args) {List<String> list = new CopyOnWriteArrayList<>(Arrays.asList("a","b","c","d"));for (String s : list) {if ("c".equals(s)) list.remove(s); // 安全且不会抛出 CME}System.out.println(list); // [a, b, d]}
}


