首页 > 解决方案 > Java - 铸造 Set 的缺点?

问题描述

我正在用 Java 开发一个基本的基于实体组件系统的游戏引擎,以获得乐趣。

我需要一种快速的方法来获取Component某种类型的所有 s。例子:

public class MovementSystem extends System {
    @Override
    public void update() {
        Set<Location> locations = ComponentManager.getActiveComponents(Location.class);
        for (Location locationComponents : locations) {
            // do stuff
        }
    }
}

所以我有一个ComponentManager应该处理这个的:

public class ComponentManager {
    private static final Map<Class<? extends Component>, Set<Component>> active = new HashMap<>();

    public static void activate(Component component) {
        // if component.class does not have an entry in
        // active yet, initialize with new HashSet
        active.computeIfAbsent(component.getClass(), compClass -> new HashSet<>());
        active.get(component.getClass()).add(component);
    }
    public static void deactivate(Component component) {
        Set<Component> comps = active.get(component.getClass());
        if (comps != null)
            comps.remove(component);
    }
    public static <T extends Component> Set<T> getActiveComponents(Class<T> compClass) {
        return (Set<T>) active.get(compClass);
    }
}

我的 IDE 不喜欢(Set<T>) active.get(compClass);(以黄色突出显示)。然而,这是基于非常基本的测试,并且比单独铸造每个项目Set并将它们添加到新的更快Set,但是这样做的潜在缺点是什么?

标签: javacastingsetentityentity-component-system

解决方案


您看到的 IDE 警告是未经检查的转换。

在这里,您将返回分配getActiveComponents()给 a Set<Location>

Set<Location> locations = ComponentManager.getActiveComponents(Location.class);

但是根据对象的来源,它可能是任何类型的Components (你从 a 中获取Set<Component>),不一定Location是 s :

private static final Map<Class<? extends Component>, Set<Component>> active = new HashMap<>();

当你使用它时:

 return (Set<T>) active.get(compClass);

但是这样做的潜在缺点是什么?

今天,您的代码是正确的,因为它在Map条目中添加了值,其中对象Set 具有用作键的类的运行时类型。
但是,如果稍后代码在映射中添加了不遵循此规则的内容,编译器将无法检测到打字错误。
所以你只会在运行时发现它:这是缺点。

你的情况不好吗?这取决于。
当您开发某种按类型/类别操作/分组对象的库时,可能会发生这种问题。您通常必须研究类型安全和代码灵活性/可维护性之间的平衡。
在这里,您“失去”类型安全性以减少维护结构的数量。
如果您的子类数量有限Component并且它不会移动,则可以将地图分解为几组:每个子类一组。这样,您将不再有未经检查的转换。
但如果数量Component子类很重要并且它会移动(您可以随时添加或删除它),未经检查的转换警告可能是可以接受的。在这种情况下,非常欢迎编写一个单元测试来确保您只检索预期类型的​​实例,因为这样可以保证当您执行未经检查的转换时编译器无法为您执行此操作。


推荐阅读