简体   繁体   中英

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> List<? extends Animal> as a List<Animal> ? Is that has something to do with the type erasure mechanism?

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> 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> 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.

Because List<? extends Animal> List<? extends Animal> would allow any subclass of Animal. List would simply allow objects of the Animal class.

In List<? extends Animal> List<? extends Animal> are also objects like cat or dog allowed. If you initiate this with a "pure" dog-list, you can't tell from the outside that it's not allowed and therefore it doesn't compile.

Co-, Contra- & Invariance in Java

This is about Co-, Contra- and Invariance. Covariance tells us about what we can take out, Contravariance tells us about what we can put in, and Invariance tells us about both.

Invariance

List<Animal> is invariant . You can add any Animal, and you are guaranteed to get any Animal out - get(int) gives us an Animal , and add(Animal) must accept any Animal. We can put an Animal in, we get an Animal out.

List<Animal> animals = new ArrayList<Dog>() is a compiler error , since it doesn't accept Animal or Cat . get(int) still gives us only Animals (Dogs are Animals, after all), but not accepting the others is a deal-breaker.

List<Animal> animals = new ArrayList<Object>() is likewise a deal-breaker. Yes, it accepts any animal (we can put Animals in), but it gives us Objects.

Contravariance

List<? super Dog> List<? super Dog> is contravariant . We can only put Dogs in, put nothing is said about what we get out. Thus, we get Object out.

List<? super Dog> dogs = new ArrayList<Animal>(); this works, because we can put a Dog into it. And Animals are Objects, so we can get objects out.

List<? super Dog> dogs = new ArrayList<Animal>();
// dogs.add(new Animal()); // compile error, need to put Dog in
dogs.add(new Dog());
Object obj = dogs.get(0);
// Dog dog = dogs.get(0); // compile error, can only take Object out

Covariance

List<? extends Animal> List<? extends Animal> is covariant . You are guaranteed to get an Animal out .

List<? extends Animal> animals = new ArrayList<Cat>(); works, because cats are Animals, and get(n) gives you Animals. Granted, they are all Cats, but Cats are Animals, so this works out fine.

Adding stuff is harder, though, since you don't actually have a type that you can put in:

List<? extends Animal> animals = new ArrayList<Cat>();
//animals.add(new Cat()); // compile error
//animals.add(new Animal()); // compile error
Animal animal = animals.get(0);

List<? extends Cat> cats = new ArrayList<Animal>(); is a compiler error , because you can take out any animal - but you require that the only thing that can be taken out is Cats.

Your code

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

Here, everything is fine. foo() is a List where you can take out Animals. You surely Since Dogs are Animals, and you can take out Dogs, you you can take out Animals. Everything you take out of the List is guaranteed to be an Animal.

List<Animal> dogs = Main.foo(); // compile error

You are saying that dogs is a List where you can put in any Animal , and you are guaranteed to get Animals out. The last part is easy, yes, you are guaranteed to get Animals out, that is what ? extends Animal ? extends Animal means. But you can't put arbitrary Animals in. And that is why this fails.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM