Home > front end >  Java Method that returns different types of generic Lists
Java Method that returns different types of generic Lists

Time:11-08

I'm currently trying to write a method that goes trough a list of Ant-Objects and returns a list of AntScouts, that extend Ant. In genereal, List can contain a lot of diffrent 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;
        };
    }
}

(That enum currently has the "Raw use of parameterized class 'Class'" warning)

And this is the method that currently returns a List.

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 or List corresponding to the enum? I REALLY don't want to use Class 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

CodePudding user response:

I would change your AntType enum method so that it acts as the filter in your stream. I've had to guess at the rest of the class hierarchy but this might give you a starting point.

import java.util.List;
import static java.util.stream.Collectors.toList;
public class Demo {
    private List<Ant> ants = List.of(
            new AntQueen(),
            new AntScout(),
            new AntGatherer(),
            new AntWarrior());

    public static void main(String[] args) {
        var demo = new Demo();
        System.out.println(demo.getAntsType(AntType.QUEEN));
    }

    public List<Ant> getAntsType(AntType type) {
        return ants.stream().filter(type::matches).collect(toList());
    }
}

class Ant {}
class AntQueen extends Ant {}
class AntWarrior extends Ant {}
class AntGatherer extends Ant {}
class AntScout extends Ant {}

enum AntType {
    QUEEN, WARRIOR, GATHERER, SCOUT;

    public boolean matches(Ant a) {
        return switch (this) {
            case QUEEN -> a instanceof AntQueen;
            case WARRIOR -> a instanceof AntWarrior;
            case GATHERER -> a instanceof AntGatherer;
            case SCOUT -> a instanceof AntScout;
        };
    }
}

CodePudding user response:

This could be possible if enums could be generic, but they can't. However, that is no big deal. Just use a final class with a bunch of public static final fields and a private constructor. A little verbose surely, but is as effective as an enum.

Also, your getClass() method should either be a static method with the switch or else be an instance method without the switch. The later is much better, so went that way. Further, calling it getClass() is not a good idea since it is unrelated with Object.getClass() method. So I called it getAntTypeClass().

And this is the result:

public class Main {
    public static void main(String[] args) {
        System.out.println(AntType.QUEEN.getAntTypeClass().getName());
        System.out.println(AntType.SCOUT.getAntTypeClass().getName());
    }
}

final class AntType<T extends Ant> {
    public static final AntType<AntQueen>    QUEEN    = new AntType<>(AntQueen.class   );
    public static final AntType<AntWarrior>  WARRIOR  = new AntType<>(AntWarrior.class );
    public static final AntType<AntGatherer> GATHERER = new AntType<>(AntGatherer.class);
    public static final AntType<AntScout>    SCOUT    = new AntType<>(AntScout.class   );

    private final Class<T> antTypeClass;

    private AntType(Class<T> antTypeClass) {
        this.antTypeClass = antTypeClass;
    }

    public Class<T> getAntTypeClass() {
        return antTypeClass;
    }
}

interface Ant {}
class AntWarrior implements Ant {}
class AntGatherer implements Ant {}
class AntScout implements Ant {}
class AntQueen implements Ant {}

See it working on ideone.

CodePudding user response:

There are a couple ways you can do this.

First, fix the method in your enum:

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

    public Class<? extends Ant> getImplClass(){
        return switch (this) {
            case QUEEN -> AntQueen.class;
            case WARRIOR -> AntWarrior.class;
            case GATHERER -> AntGatherer.class;
            case SCOUT -> AntScout.class;
        };
    }
}

Since this is a non static method, you don't need to take in the type as an argument. In your example it's not clear where the list is coming from, but if I add it as an argument, it would look like this:

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

The second way to do it would be to add a method called getType() in the Ant class which returns a type variable that is set by the constructor.

public class Ant {
    private AntType type;

    protected Ant(AntType type) {
        this.type = type;
    }

    public AntType getType() {
        return type;
    }
}

Then you set the type in each of the subclass's constructors:

public class AntQueen extends Ant {
    protected AntQueen() {
        super(AntType.QUEEN);
    }
}

Then the filtering code looks like this:

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

CodePudding user response:

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 initial version returning a List of supertype 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 you'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;
}
  • Related