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