Java - 施展 Set 的缺点?
Java - downsides to casting a Set?
为了好玩,我正在 Java 开发基于实体组件系统的基本游戏引擎。
我需要一种快速获取特定类型的所有 Component
的方法。
示例:
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
更快,但是这样做的潜在缺点是什么?
您看到的 IDE 警告是未经检查的转换。
在这里,您将 getActiveComponents()
的 return 分配给 Set<Location>
:
Set<Location> locations = ComponentManager.getActiveComponents(Location.class);
但是根据对象的来源,它可能是任何类型的Component
s(你从Set<Component>
中取出),不一定是Location
s :
private static final Map<Class<? extends Component>, Set<Component>> active = new HashMap<>();
当你使用它时:
return (Set<T>) active.get(compClass);
but what are the potential downsides to doing this?
今天您的代码是正确的,因为它在 Map
条目中添加了值,其中 Set
的对象将 class 的运行时类型用作键。
但是如果稍后代码在映射中添加不遵循此规则的内容,编译器将无法检测到键入错误。
所以你只会在运行时发现它:这是缺点。
你的情况不好吗?视情况而定。
当您开发 manipulate/group 对象为 type/category 的某种库时,可能会发生此类问题。你一般要研究类型安全和代码之间的平衡flexibility/maintainability.
在这里你 "lose" 键入安全以减少要维护的结构的数量。
如果您的 Component
subclasses 数量有限并且它不会移动,您可以将地图分解成几组:每个 subclass 一组。这样,您就不会再有未经检查的转换了。
但是,如果 Component
subclasses 的数量很重要并且它会移动(您可以及时添加或删除它),则未经检查的转换警告可能是可以接受的。在这种情况下,非常欢迎编写一个单元测试来确保您只检索预期类型的实例,因为这提供了编译器在您执行未经检查的转换时无法为您做的保证。
为了好玩,我正在 Java 开发基于实体组件系统的基本游戏引擎。
我需要一种快速获取特定类型的所有 Component
的方法。
示例:
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
更快,但是这样做的潜在缺点是什么?
您看到的 IDE 警告是未经检查的转换。
在这里,您将 getActiveComponents()
的 return 分配给 Set<Location>
:
Set<Location> locations = ComponentManager.getActiveComponents(Location.class);
但是根据对象的来源,它可能是任何类型的Component
s(你从Set<Component>
中取出),不一定是Location
s :
private static final Map<Class<? extends Component>, Set<Component>> active = new HashMap<>();
当你使用它时:
return (Set<T>) active.get(compClass);
but what are the potential downsides to doing this?
今天您的代码是正确的,因为它在 Map
条目中添加了值,其中 Set
的对象将 class 的运行时类型用作键。
但是如果稍后代码在映射中添加不遵循此规则的内容,编译器将无法检测到键入错误。
所以你只会在运行时发现它:这是缺点。
你的情况不好吗?视情况而定。
当您开发 manipulate/group 对象为 type/category 的某种库时,可能会发生此类问题。你一般要研究类型安全和代码之间的平衡flexibility/maintainability.
在这里你 "lose" 键入安全以减少要维护的结构的数量。
如果您的 Component
subclasses 数量有限并且它不会移动,您可以将地图分解成几组:每个 subclass 一组。这样,您就不会再有未经检查的转换了。
但是,如果 Component
subclasses 的数量很重要并且它会移动(您可以及时添加或删除它),则未经检查的转换警告可能是可以接受的。在这种情况下,非常欢迎编写一个单元测试来确保您只检索预期类型的实例,因为这提供了编译器在您执行未经检查的转换时无法为您做的保证。