Java游戏实体系统:遍历容器时增删元素的优雅解决方案
解决方案:游戏实体系统遍历增删的优雅处理
方案1:使用CopyOnWriteArrayList直接遍历修改
CopyOnWriteArrayList是Java并发包中的集合类,它在执行修改操作时会创建底层数组的副本,遍历操作基于原始数组的快照进行,因此不会抛出ConcurrentModificationException。适合实体数量较少的场景,因为每次修改都会复制数组,性能开销随实体数量增加而上升。
修改Manager类的集合类型即可:
import java.util.concurrent.CopyOnWriteArrayList; public class Manager { CopyOnWriteArrayList<Entity> entities; public Manager() { this.entities = new CopyOnWriteArrayList<>(); } public void update() { for (Entity e : entities) { e.update(); } } public void addNew() { entities.add(new Entity(this)); } public void removeRandom() { if (!entities.isEmpty()) { entities.remove((int) (Math.random() * entities.size())); } } public CopyOnWriteArrayList<Entity> getEntities() { return entities; } }
方案2:分阶段处理(游戏场景推荐)
游戏开发中更合理的流程是先处理所有实体的逻辑,再统一执行增删操作,既避免并发修改异常,又符合游戏帧更新的常规逻辑(新生成的实体不会在当前帧参与逻辑计算)。
修改Manager类,新增操作队列:
import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; public class Manager { private List<Entity> entities; private List<Consumer<List<Entity>>> pendingOperations; public Manager() { this.entities = new ArrayList<>(); this.pendingOperations = new ArrayList<>(); } public void update() { // 1. 遍历当前所有实体执行更新逻辑 for (Entity e : entities) { e.update(); } // 2. 统一执行所有待处理的增删操作 for (Consumer<List<Entity>> op : pendingOperations) { op.accept(entities); } pendingOperations.clear(); } public void addNew() { pendingOperations.add(list -> list.add(new Entity(this))); } public void removeRandom() { pendingOperations.add(list -> { if (!list.isEmpty()) { list.remove((int) (Math.random() * list.size())); } }); } public List<Entity> getEntities() { return entities; } }
这种方式的优势:
- 逻辑清晰,完全贴合游戏帧更新的流程
- 性能优于
CopyOnWriteArrayList,尤其当实体数量较多时 - 可灵活扩展批量增删、条件筛选等复杂操作
方案3:函数式风格的实体更新
如果偏好函数式写法,可以通过快照遍历+操作收集的方式实现,避免直接修改原集合:
import java.util.ArrayList; import java.util.List; public class Manager { private List<Entity> entities; public Manager() { this.entities = new ArrayList<>(); } public void update() { // 复制当前实体列表作为快照,避免遍历中修改原集合 List<Entity> currentEntities = new ArrayList<>(entities); List<Runnable> pendingActions = new ArrayList<>(); // 遍历快照处理实体逻辑,收集增删操作 currentEntities.forEach(e -> { e.update(); // 可根据实体状态收集自定义操作,比如标记删除、生成新实体等 }); // 统一执行收集到的操作 pendingActions.forEach(Runnable::run); } public void queueAdd() { entities.add(new Entity(this)); } public void queueRemoveRandom() { if (!entities.isEmpty()) { entities.remove((int) (Math.random() * entities.size())); } } }
关键注意点
- 普通
ArrayList的迭代器不允许遍历过程中直接修改集合(仅允许迭代器自身的remove方法),否则会触发并发修改异常 - 分阶段处理是游戏开发的通用方案,逻辑可控性更强;
CopyOnWriteArrayList适合快速实现小体量场景 - 分阶段处理时,新增实体不会在当前帧参与逻辑计算,这通常符合游戏的预期行为
内容的提问来源于stack exchange,提问作者Calle Jonasson
相关产品推荐
相关产品推荐

