Skip to content
Advertisement

Java Method that returns different types of generic Lists

I’m currently trying to write a method that goes through a list of Ant-Objects and returns a list of AntScouts, that extend Ant. In general, List<Ant> can contain a lot of different Objects that inherit from Ant.

I also have an enum for the different kinds of ants:

public enum AntType {
    QUEEN,WARRIOR,GATHERER,SCOUT;

    public Class getClass(AntType type){
        return switch (type) {
            case QUEEN -> AntQueen.class;
            case WARRIOR -> AntWarrior.class;
            case GATHERER -> AntGatherer.class;
            case SCOUT -> AntScout.class;
        };
    }
}

This enum causes a warning:

Raw use of parameterized class 'Class'

And this is the method that currently returns a List<Ant>.

public List<Ant> getAntsType(AntType type){
    return ants.stream().filter(ant -> ant.getType() == type).toList();
}

How can I write the method so that it get’s the AntType enum as argument and returns a List<AntScout> or List<AntWarrior> corresponding to the enum? I REALLY don’t want to use Class<T> clazz as argument since that would defeat the point of the enum. (I also use that enum elsewhere, so I can’t get rid of it)

How can I write the method so that it get’s the AntType enum as argument and returns a List or List corresponding to the enum?

Edit: This comment probably comes closest to the desired solution: Java Method that returns different types of generic Lists

Advertisement

Answer

Use the Power of Polymorphism

How can I write the method so that it get’s the AntType enum as argument and returns a List or List corresponding to the enum?

You’re overengineering your code for no good reason.

When you’re using inheritance, your classes should be designed in a way that allow to benefit from the Polymorphism.

I.e. by using super type Ant for all your objects and interacting with them through overridden behavior without a need to discriminate between the concrete implementations and operating via type casts.

Therefore, your method returning List<Ant> is quite fine.

And even if you wanted to obtain a List<AntQueen> or List<AntScout> as a result of the method execution then you would need a to use a generic type variable T, or rather T extends Ant, and that would imply that you need a mean of representing the T. And enum would not help you with this task because in Java enums can’t be generic. You need to provide as a method argument either an instance of T or a Class<T>.

public <T extends Ant> List<T> getAntsByType(Class<T> tClass) {
    return ants.stream().filter(tClass::isAssignableFrom).toList();
}

But I would advise sticking with the initial version returning a List of super type Ant declaring method getType() which returns an instance of enum AntType.

public List<Ant> getAntsByType(AntType type) {
    return ants.stream().filter(ant -> ant.getType() == type).toList();
}

And as I’ve said, Java-enums can’t be generic, there’s no way to obtain Class<T> through it. Hence, you can remove contrived method getClass() from AntType.

public enum AntType {
    QUEEN, WARRIOR, GATHERER, SCOUT;
}

Simulated self-type

But if you’re still convinced that your application logic require the ability to generate a list of concrete type like List<AntScout> from a list of super type, then you can make use of a recursive type bound.

For that, you need to define the super type as Ant<T extends Ant<T>>.

This approach is also called a simulated self-type idiom and can be observed in the declaration of the parent type of all enums java.lang.Enum<E extends Enum<E>> and in some other parts of the JDK like method Collections.sort(List<T>) where T is defined as <T extends Comparable<? super T>>.

Let’s apply self-type idiom for this case.

Consider super type Ant defined as an interface, declaring a self-returning method (you can change into abstract class if you need to declare some skeletal implementations and common fields):

interface Ant<T extends Ant<T>> {
    T self();
    AntType getType();
}

And here’s a couple of concrete classes:

public static class AntWarrior implements Ant<AntWarrior> {
    
    @Override
    public AntWarrior self() {
        return this;
    }
    
    @Override
    public AntType getType() {
        return AntType.WARRIOR;
    }
}

public static class AntScout implements Ant<AntScout> {
    @Override
    public AntScout self() {
        return this;
    }
    
    @Override
    public AntType getType() {
        return AntType.SCOUT;
    }
}

That how we can perform conversion using self() method:

@SuppressWarnings("unchecked")
public static <T extends Ant<T>> List<T> getAntsByType(List<Ant<?>> ants,
                                                       AntType type) {
    return ants.stream()
        .filter(ant -> ant.getType() == type)
        .map(ant -> (T) ant.self())
        .toList();
}

Usage example:

public static void main(String[] args) {
    List<Ant<?>> ants = List.of(new AntWarrior(), new AntScout());

    // compiles and runs without issues
    List<AntWarrior> antWarriors = getAntsByType(ants, AntType.WARRIOR);
    System.out.println(antWarriors);

    // compiles and runs without issues
    List<AntScout> antScouts = getAntsByType(ants, AntType.SCOUT);
    System.out.println(antScouts);
}

Output:

[AntWarrior{}]
[AntScout{}]

A link to Online Demo

Advertisement