为什么不能列出 <? extends Animal> 被 List<Animal> 取代?

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

考虑以下代码:

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

我想了解为什么它无法编译。意思是,为什么编译器不让我将 List<? extends Animal> 称为 List<Animal>? 这与类型擦除机制有关吗?

A List<Animal> 是一个 List,您可以向其中添加 any Animal(或 null),以及您从中取出的所有内容它将是 Animal.

一个List<? extends Animal>是一个只包含Animal(或null)的特定子类的列表,你不知道是哪一个;这使您可以将从中取出的所有内容都视为 Animal,但不允许向其中添加任何内容(文字 null 除外)。


A List<? extends Animal> 不能充当 List<Animal>,因为那会让你这样做:

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

现在,由于 listOfCatslistOfSomeAnimalslistOfAnimals 都是同一个列表,因此 Dog 已添加到 listOfCats。因此:

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

因为 List<? extends Animal> 允许 Animal 的任何子class。 List 只允许 Animal class.

的对象

List<? extends Animal>中也允许使用猫或狗之类的物体。如果您使用“纯”dog-list 启动它,您无法从外部判断它是不允许的,因此它不会编译。

Java

中的协、反和不变性

这是关于 Co-、Contra- 和 Invariance。协变告诉我们可以取出什么,逆变告诉我们可以放入什么,不变性告诉我们两者。

不变性

List<Animal>不变的。您可以添加任何 Animal,并且您一定会得到 any 个 Animal - get(int) 给我们一个 Animal,并且 add(Animal) 必须接受 任何 动物。我们可以放一个 Animal 进去,然后放一个 Animal 出来。

List<Animal> animals = new ArrayList<Dog>() 是一个 编译器错误,因为它不接受 AnimalCatget(int) 仍然只给我们 Animals(狗毕竟是动物),但不接受其他的是 deal-breaker.

List<Animal> animals = new ArrayList<Object>()同样是deal-breaker。是的,它接受任何动物(我们可以把动物放进去),但它给我们对象。

逆变

List<? super Dog>逆变。我们只能把Dogs放进去,放出来什么都说不准。这样,我们就得到了Object。

List<? super Dog> dogs = new ArrayList<Animal>(); 这行得通,因为我们 可以 把狗放进去。而Animals是Objects,所以我们可以把objects取出来。

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

协方差

List<? extends Animal> 协变的 。您一定会得到一只动物 out.

List<? extends Animal> animals = new ArrayList<Cat>(); 有效,因为猫是动物,而 get(n) 给你动物。当然,它们都是猫,但猫是动物,所以这很好。

不过,添加东西比较困难,因为您实际上没有可以放入的类型:

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>(); 是一个编译器错误,因为你可以取出任何动物 - 但你要求唯一可以取出的是猫。

您的代码

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

在这里,一切都很好。 foo() 是一个列表,您可以在其中 取出 动物。你肯定因为狗是动物,你可以消灭狗,你也可以消灭动物。您从列表中取出的所有东西都保证是动物。

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

你是说 dogs 是一个列表,你可以在其中 放入 any Animal,并且你保证把动物赶出去。最后一部分很简单,是的,你保证能把Animals弄出来,这就是? extends Animal的意思。但是你不能随意放入动物。这就是失败的原因。