Search code examples
javajava-8polymorphismcovariance

Finding All Objects that are a Child of a Given Type in Java


I'm currently writing a grid-based puzzle game, and have run into a problem.

I have an abstract Sprite class where each Sprite represents some object on my map (a player, a wall, etc). This Sprite class has children (Item, Entity, Obstacle), and these children have children (Item has InventoryItem and InstantUseItem), and so on. Only children without any further children are not abstract, so you can only instantiate concrete objects that you can find in-game (you can instantiate Sword and Arrow, but not Weapon - their parent object).

My problem is that I'm storing all the objects on Tiles (each map has width*height Tiles) in a Sprite ArrayList, and now I want to do something like find which Tile the Player object is on, or find all the Tiles that contain Enemy objects (or classes that inherit from them).

The problem is that, as far as I can tell, I can't do something akin to this in my Map or Tile object:

public ArrayList<t> findAllSpritesOfType(Type t) {
    ArrayList<t> sprites = new ArrayList<t>();
    for(Sprite s : this.getSprites()) {
        if(s instanceof t) {
            sprites.add((t) s); //Add after casting to right class
        }
    }
    return sprites;
}

Even if I try to implement a static function in Sprite, I would need this (a kind of 'automatic covariance' amongst all of Sprite's children):

public static ArrayList<this.Type> getSpritesOnTile(Tile t) {
    ArrayList<this.Type> sprites = new ArrayList<this.Type>();
    for(Sprite s : t.getSprites()) {
        if(s instanceof this.Type) {
            sprites.add((this.Type) s); //Add after casting to right class
        }
    }
    return sprites;
}

The other methods I have thought of involve:

  1. Making the latter method return an array of Sprites, then overriding this method in all children to return an array of children using covariance.
  2. Making Sprite contain isWeapon(), isItem(), isEntity(), etc, (which all return false) then override these methods in the appropriate children. I now use these methods instead of a unified isGivenType(Type t) method, or a unified static Type.isGivenType() method.

Making sure my program conforms to Object Oriented Principles, and remains easily extensible, is very important to this project. Is there any way to implement any of my solutions, or achieve my goal?


Solution

  • It looks like this is what you want:

    public <T extends Sprite> ArrayList<T> findAllSpritesOfType(Class<T> clazz) {
        ArrayList<T> sprites = new ArrayList<>();
        for(Sprite s : this.getSprites()) {
            if(clazz.isInstance(s)) {
                sprites.add((T) s);
            }
        }
        return sprites;
    }
    

    And then you can use it like this:

    List<Item> items = findAllSpritesOfType(Item.class);
    

    Another way to do it would be to return a stream instead of a list:

    public <T extends Sprite> Stream<T> findAllSpritesOfType(Class<T> clazz) {
        return getSprites().stream().filter(clazz::isInstance).map(clazz::cast);
    }