Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java - Passing generic lists into class via constructor

I've been trying to play with generics and polymorphism. I've come across a problem I can't get my head around.

Say I have

public abstract class Animal {
    private age;
    private weight;

    public Animal(){}

    public void Move(){
        //stuff
    }

    public void Eat(){
        //stuff
    }

    //Getters and setters
}

And then

public Cat extends Animal {
    //Constructors
}

public Snake extends Animal {
    //Constructors
    public void ShedSkin(){
        //stuff
    }
}

I also have my own class "MyAbstractClass". It's here I can't seem to work things out.

public abstract class MyAbstractClass {
    private List<Animal> myAnimalList;

    public MyAbstractClass(){}

    public MyAbstractClass(List<? extends Animal> animalList) {
        this.myAnimalList = animalList;
    }

    public List<Animal> getAnimalList() {
        return myAnimalList;
    }

    //Stuff which DOES NOT add to the list
}

I want to be able to do:

public class MyCatClass extends MyAbstractClass {
    public MyCatClass(List<Cat> catList) {
        super(catList);
    }
}

public class MySnakeClass extends MyAbstractClass {
    public MySnakeClass(List<Snake> snakeList) {
        super(snakeList);
    }

    public ShedSkin(){
        (Snake)getAnimalList().get(0).ShedSkin(); //Dont worry about indexing
    }
}

Then

public static void main(string[] args) {
    //Make list of cats
    //Make list of snakes

    MyCatClass cats = new MyCatClass(catList);
    MySnakeClass snakes = new MySnakeClass(snakeList);

    snakes.ShedSkin();
}

This does not work for me. It fails to compile at public MyAbstractClass(List<? extends Animal> animalList). This is a bit over my java experience and would greatly appreciated some guidance. I hope the example classes are made clear enough to understand my intention.

Compiler tells me: java.util.List<Animal> in MyAbstractClass cannot be applied to java.util.List<capture<? extends Animal>> Thanks.

Edit: Looks like I'll have to spell out what I'm trying to do. I have a list of animals which may or may not have more fields and methods than the superclass, but they all extend the superclass. I also want a class which takes in a list of a single type of animal, and call methods on or over each element in the List. I won't be adding anything to the list so that shouldn't be a problem. The reason I'm splitting up like this is because between all the Animals, there is alot of similarity, but some animals have specialties. The class taking in the List needs to be able to handle these particularities. Hence have the worker class extend a superclass and implement extra methods.

EDIT2: Worked it out!

public abstract class MyAbstractClass {
        private List<? extends Animal> myAnimalList;

        public MyAbstractClass(){}

        public MyAbstractClass(List<? extends Animal> animalList) {
            this.myAnimalList = animalList;
        }

        public List<Animal> getAnimalList() {
            return myAnimalList;
        }

        //Stuff which DOES NOT add to the list
    }

Both the field and the constructor need to be List<? extends Animal> foobar. Constructive brainstorming session!

EDIT3: Dima's method is better.

like image 508
John Geddes Avatar asked Mar 18 '23 12:03

John Geddes


2 Answers

Well, you can't assign List<? extends Animal> to a List<Animal>, because lists are not covariant: the former is not a subclass of the latter.

You need to change the declaration of the base class member (and the return type of getAnimalList()) to List<? extends Animal>. You mentioned in one of your comments that doing so gives you some kind of other error in the MyCatClass, but you must be mistaken, that class should be fine if everything in base class is declared properly (not List<Animal>, but List<? extends Animal>).

This line: (Snake)getAnimalList().get(0).ShedSkin() is, probably, the one, that was causing you troubles. First, you need a pair of parenthesis around the snake: ((Snake) getAnimalList().get(0)).ShedSkin(), and second, again, you cannot cast List<Animal> to List<Snake>, make sure getAnimalList() is declared to return List<? extends Animal>, then everything should compile.

A better alternative in your case is, I think, to parametrize the base class:

public abstract class MyAbstractClass<T extends Animal> {
    private List<T> myAnimalList;
    public MyAbstractClass(List<T> animals) { myAnimalList = animals; }
    public List<T> getAnimalList() { return myAnimalList; }
    //etc.
}

public class MyCatClass extends MyAbstractClass<Cat> {
     public MyCatClass(List<Cat> cats) { super(cats); }
}
public class MySnakeClass extends MyAbstractClass<Snake> {
     public MySnakeClass(List<Snake> snakes) { super(snakes); }
     public ShedSkin() { getAnimalList().get(0).ShedSkin(); }
}

etc. This way you don't need to resort to casting anywhere, because the type of the list is always known exactly.

like image 155
Dima Avatar answered Mar 23 '23 15:03

Dima


You have to change this: private List<Animal> myAnimalList to private List<? extedns Animal> myAnimalList

and

public List<Animal> getAnimalList() {
        return myAnimalList;
    } to
 public List<?extends Animal> getAnimalList() {
            return myAnimalList;
        }

Main reason is Cat extends Animal but List<Cat> do not extends List<Animal>. java generics will not allow to List<Cat> to List<Animal>. example: You can not do this: List<Animal> animalList= new ArrayList<Cat>(); java will not allow this. Cause otherwise Snake can be added animalList which ambiguous. But you can cast like this: List<? extends Animal> animalList= new ArrayList<Cat>(); In this casting you can only call the function of Animal class. As a result you can add any type of animal in animalList.

like image 30
Md. kamrul Hasan Avatar answered Mar 23 '23 15:03

Md. kamrul Hasan