Java游戏中遍历LinkedList触发ConcurrentModificationException的解决方法
嘿,这问题太常见了!你碰到的是Java集合迭代时的经典坑——增强for循环(也就是for-each)依赖的迭代器不允许在遍历过程中直接修改集合结构(比如remove/add操作),而你的代码刚好踩了这个雷。
问题根源拆解
你在InGame.tick()里用增强for遍历entities这个LinkedList,而遍历到Block对象时,它的tick()方法直接调用了inGame.entities.remove(this)——这会修改LinkedList的modCount(集合修改次数计数器),但当前迭代器维护的expectedModCount还是原来的值,两者不匹配,就触发了ConcurrentModificationException。
而且你的getSurrounding()方法还嵌套遍历了同一个集合,这只会让问题的触发概率更高,但核心原因还是遍历中直接修改集合。
三种可行解决方案
1. 标记待删除元素,遍历后批量移除(推荐,性能友好)
这种方式的思路是把移除操作和遍历操作分开,避免遍历过程中修改集合:
- 给
Entity类加一个删除标记字段和对应的方法:
public class Entity { private boolean markedForRemoval = false; public void markForRemoval() { markedForRemoval = true; } public boolean isMarkedForRemoval() { return markedForRemoval; } }
- 修改
Block.tick(),把直接移除改成标记自己:
@Override public void tick(float delta) { // 保留你原来的周围方块判断逻辑... if (strength <= 0) { this.markForRemoval(); // 标记为待删除,不再直接调用集合remove } }
- 最后修改
InGame.tick(),先遍历所有实体执行逻辑,再批量移除标记的元素:
@Override public void tick(float delta) { List<Entity> toRemove = new ArrayList<>(); // 遍历执行tick,收集待删除元素 for (Entity e : entities) { e.tick(delta); if (e.isMarkedForRemoval()) { toRemove.add(e); } } // 遍历完成后统一移除 entities.removeAll(toRemove); player.tick(delta); }
2. 使用显式迭代器的remove()方法(适合外部判断移除的场景)
如果是在遍历的外部逻辑中决定是否移除元素,而不是元素自己触发,可以用显式的Iterator遍历,调用迭代器的remove()方法——这个方法会同步更新modCount和expectedModCount,不会触发异常:
@Override public void tick(float delta) { Iterator<Entity> iterator = entities.iterator(); while (iterator.hasNext()) { Entity e = iterator.next(); e.tick(delta); // 如果是外部判断条件,比如这里判断Block是否要移除 if (e instanceof Block && ((Block)e).getStrength() <= 0) { iterator.remove(); // 用迭代器的remove方法,而不是集合的remove } } player.tick(delta); }
不过这种方式对你的场景不太适配,因为你的移除逻辑是在Block内部的tick()里,所以还是第一种标记法更合适。
3. 使用支持并发修改的集合(最省事,适合小体量游戏)
把LinkedList换成CopyOnWriteArrayList,这个集合的特点是遍历的时候会使用集合的快照,所有修改操作都会复制一份新的数组,不会影响正在进行的遍历,自然也就不会抛出异常:
// 把entities的声明改成CopyOnWriteArrayList private List<Entity> entities = new CopyOnWriteArrayList<>();
这种方式不需要修改你原来的tick()和Block的移除逻辑,直接替换集合类型就行。缺点是每次修改都会复制数组,性能开销比LinkedList大,适合你的小游戏这种实体数量不多的场景。
最后再提一句
增强for循环看起来简洁,但本质是Iterator的语法糖,只要是遍历集合时修改集合结构(除了迭代器自己的remove()),都会触发这个异常——记住这个坑,以后就不会再踩啦!
内容的提问来源于stack exchange,提问作者Ruben de Groot

