Search code examples
javaoopgenericscovariancecontravariance

Why can't List<? extends Animal> be replaced with List<Animal>?


Consider the following code:

public class Main {

    static class Animal {}

    static class Dog extends Animal {}

    static List<? extends Animal> foo() {
        List<Dog> dogs = new ArrayList<>();
        return dogs;
    }

    public static void main(String[] args) {
        List<Animal> dogs = Main.foo(); // compile error
    }
}

I'm trying to understand why it won't compile. Meaning, why doesn't the compiler let me refer to List<? extends Animal> as a List<Animal>? Is that has something to do with the type erasure mechanism?


Solution

  • A List<Animal> is a List to which you can add any Animal (or null), and everything you take out of it will be an Animal.

    A List<? extends Animal> is a list which contains only a specific subclass of Animal (or null), and you don't know which one; this allows you to treat everything you take out of it as an Animal, but you aren't allowed to add anything to it (except for literal null).


    A List<? extends Animal> can't act as a List<Animal>, because that would allow you to do this:

    List<Cat> listOfCats = new ArrayList<>();
    List<? extends Animal> listOfSomeAnimals = listOfCats;  // Fine.
    List<Animal> listOfAnimals = listOfSomeAnimals;  // Error, pretend it works.
    listOfAnimals.add(new Dog());
    

    Now, because listOfCats, listOfSomeAnimals and listOfAnimals are all the same list, the Dog has been added to listOfCats. As such:

    Cat cat = listOfCats.get(0);  // ClassCastException.