为什么不能列出 <? 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());
现在,由于 listOfCats
、listOfSomeAnimals
和 listOfAnimals
都是同一个列表,因此 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>()
是一个 编译器错误,因为它不接受 Animal
或 Cat
。 get(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
的意思。但是你不能随意放入动物。这就是失败的原因。
考虑以下代码:
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());
现在,由于 listOfCats
、listOfSomeAnimals
和 listOfAnimals
都是同一个列表,因此 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>()
是一个 编译器错误,因为它不接受 Animal
或 Cat
。 get(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
的意思。但是你不能随意放入动物。这就是失败的原因。