Search code examples
javacastingsetentityentity-component-system

Java - downsides to casting a Set?


I am working on a basic Entity Component System-based game engine in Java for fun.

I need a fast way to get all Components of a certain type. Example:

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

So I have a ComponentManager that is supposed to handle this:

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);
    }
}

My IDE does not like the (Set<T>) active.get(compClass); (highlighted in yellow). However, this works based on very rudimentary testing, and is faster than individually casting every item in the Set and adding those to a new Set, but what are the potential downsides to doing this?


Solution

  • The IDE warning that you see is a unchecked conversion.

    Here you assign the return of getActiveComponents() to a Set<Location> :

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

    But according to the source of the objects, it may be any type of Components (you take it from a Set<Component>), not necessarily Locations :

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

    As you use that :

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

    but what are the potential downsides to doing this?

    Today your code is correct since it adds in the Map entries with values where the objects of the Set have the runtime type of the class used as key.
    But if later the code adds in the map things that doesn't follow this rule, the compiler could not detect the typing error.
    So you will discover it only at runtime : that is the drawback.

    Is it bad in your case ? It depends.
    When you develop some kind of libraries that manipulate/group objects by type/category, this kind of issue may happen. You generally have to study the balance between type safety and code flexibility/maintainability.
    Here you "lose" type safety to reduce the number of structure to maintain.
    If you have a limited number of Component subclasses and that it doesn't move, you could explode the map into several sets : one for each subclass. In this way, you would not have unchecked conversion any more.
    But if the number of Component subclasses is important and that it moves (you can add or remove that in the time), the unchecked conversion warning may be acceptable. In that case, writing a unit test that ensures that you retrieve only instances of expected type is very welcome since that provides a guarantee that the compiler is not able to do for you when you perform unchecked conversion.