You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 10:22:47