5

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.

1
  • List<? extends Animal> and List<Animal> are different things. The first can be a List<Cat>, in which you cannot add a Dog, for example. The second is a list into which any Animal, cats or dogs, can be added. Mass hysteria! Apropos tutorial topic. Commented Dec 24, 2014 at 4:40

6 Answers 6

7

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks this worked, and is better than what I was doing!
Yes, "generics" is java's name for "templates". Although, at the implementation level they are entirely different (in java, it is just a syntactic sugar, all lost at run time), but functionally, from the user perspective, they are almost equivalent.
1

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.

Comments

1

There is really just the one line of code in the body of the constructor of MyAbstractClass that needs to be changed. Basically you have two options: casting the incoming list or making a (defensive) copy of the list.

If you simply keep a reference to the incoming list, all changes made to that list outside of your code will be reflected in MyAbstractClass. This may or may not be what you want. Conversely, any change you make to the list in MyAbstractClass will also affect the caller of your code. This is usually rather unexpected. If you intend to add to the list, the solution of casting the incoming list is dangerous since you do not know the actual type of the elements of the list passed to the constructor (the caller might have cast the list to List<Cat>even if it was really a List<BigCat>) - which is not a type safe operation but not forbidden. Casting inside your constructor is not type safe either, so it should probably be avoided. Nonetheless, here is the constructor with casting:

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

If MyAbstractClass wants to take control of the list of Animals, a copy of the list needs to be made. Note that copying a list does not automatically clone the elements of a list, so changes to an animal would still show up in MyAbstractClass. Here are two ways to copy the list which incidentally also solve the compilation problem (as every list of Cat or Snake is also a list of Animal, creating a copy of type List<Animal is fine - the copy only promises that all its elements are of type Animal, nothing more, nothing less):

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

You should also change the accessor to the animals list unless it is ok that every caller can modify the animal list:

public List<Animal> getAnimalList() 
    return Collections.unmodifiableList(myAnimalList);
}

If the list of animals never changes, there is an alternative solution that uses ImmutableList from Google's guava library:

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

    public MyAbstractClass(){
        this(ImmutableList.of());
    }

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

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

    //Stuff which DOES NOT add to the list
}

What makes this solution nice is that (a) the list is indeed immutable (compared to Collections.unmodifiableList which simply wraps a mutable list) and (b) ImmutableList.copyOf()is clever enough to know that when the list it receives is an ImmutableListit does not need to be copied. Because of (a), getAnimalList() can simply return the list.

BTW: the Java convention for method names is to start in lower case, for example shedSkin() rather than ShedSkin() - which is the C# convention.

Comments

0

To make that work, you must change the field in MyAbstractClass from:

private List<Animal> myAnimalList;

to

private List<? extends Animal> myAnimalList;

or change the constructor to

public MyAbstractClass(List<Animal> animalList){
    // stuff
}

4 Comments

It seems if i change the myAnimalList field to private List<? extends Animal> myAnimalList;, i get the same compiler message. Changing the constructor moves the error to my cat and snake class. So now in MySnakeClass and MyCatClass, i get: java.util.List<Animal> in MyAbstractClass cannot be applied to java.util.List<Cat>
@JohnGeddes Your code is wrong: private age; // does not have a field type
@JohnGeddes is that the issue?
No it's not an issue. This is a contrived and simplified example of what I'm trying to implement.
0

Add a generic parameter to MyAbstractClass, this way you will know type of items in the list.

Comments

-1

I just create a project with your code. please rectify below errors.

1) Import the java.util.List package.

2)  another error I find is this.. How don't know your requirement Cannot suggest the solution.

public MyAbstractClass(List<? extends Animal> animalList) {
        this.myAnimalList = animalList; <==== problem you cannot assign List<? extends Animal> to List<Animal> .. Subtyping is not allowed in generic class.
    }

One of the Solution :

public MyAbstractClass(List<? extends Animal> animalList) {

   myAnimalList= new ArrayList<Animal>();   

   for(Animal c: animalList){
        this.myAnimalList.add(c);
   }
}

2 Comments

This is a contrived example based off a project I'm working on which is much larger. The problem with making MyAbstractClass take a list of cats is that it wont be able to extended by MySnakeClass to my understanding.
I do not wish to create a new ArrayList in my abstract class. I have worked it out now though. Thanks for helping though!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.