处理ArrayList循环添加元素时的ConcurrentModificationException问题
我完全懂你遇到的麻烦——用增强for循环遍历ArrayList时,一在循环里加新元素就触发ConcurrentModificationException。这个异常其实是Java迭代器的保护机制:当迭代器正在遍历集合时,不允许直接通过集合本身的方法做结构性修改(比如add、remove这类操作),不然迭代器会认为遍历状态已经失效,直接抛出异常。
结合你的Processing代码,我给你几个实用的解决办法:
方案1:用普通for循环(倒序遍历更稳妥)
增强for循环依赖Iterator,而普通for循环直接通过索引访问元素,不会触发迭代器的修改检测。如果你的make()是往集合末尾加元素,倒序遍历还能避免索引混乱的问题:
void draw() { background(255); // 倒序遍历pipes集合 for (int i = pipes.size() - 1; i >= 0; i--) { Pipe p = pipes.get(i); p.display(); // 假设你需要显示Pipe实例 // 这里可以添加触发生成新Pipe的条件 if (p.needsNewPipe()) { make(); } } }
要是用正序遍历也可以,但得注意每次加元素后集合size会变大,如果你不想重复处理新添加的元素,倒序是更省心的选择。
方案2:用ListIterator做安全修改
ListIterator是Iterator的子类,支持遍历过程中安全地添加/修改元素,它会同步迭代器和集合的状态,不会抛出异常。你可以把增强for循环换成ListIterator遍历:
void draw() { background(255); ListIterator<Pipe> iterator = pipes.listIterator(); while (iterator.hasNext()) { Pipe p = iterator.next(); p.display(); if (p.needsNewPipe()) { // 用ListIterator的add方法直接添加元素 Pipe newPipe = new Pipe(); newPipe.create(); iterator.add(newPipe); } } }
这种方式不用改你make()的逻辑,直接在遍历里创建并添加元素就行。
方案3:遍历集合的副本
先创建原集合的副本,遍历副本的同时修改原集合——因为迭代器遍历的是副本,根本不会感知到原集合的修改:
void draw() { background(255); // 复制pipes集合到新的ArrayList ArrayList<Pipe> pipesCopy = new ArrayList<>(pipes); for (Pipe p : pipesCopy) { p.display(); if (p.needsNewPipe()) { make(); // 直接修改原集合,遍历的是副本,不会触发异常 } } }
这个方案的好处是代码改动最小,适合快速解决问题,但如果集合很大,复制副本会有一点性能开销。
为什么原代码会出问题?
你用的增强for循环(for (Pipe p : pipes))底层是靠Iterator实现的。当你在遍历过程中调用pipes.add()(也就是make()里的逻辑),集合的结构被修改了,但迭代器不知道这个变化,下次迭代时它会检测到集合的修改次数和迭代器预期的不一致,直接抛出ConcurrentModificationException。
内容的提问来源于stack exchange,提问作者Thomas Ayling

