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{}]