List<Dog>是List<Animal>的子类吗?为什么 Java 泛型不是隐式多态的?

2022-08-31 04:02:25

我对Java泛型如何处理继承/多态性有点困惑。

假设层次结构如下 -

动物(亲本)

- (儿童)

所以假设我有一个方法。根据继承和多态性的所有规则,我假设 a a,a a - 因此任何一个都可以传递给此方法。事实并非如此。如果我想实现这种行为,我必须明确地告诉该方法接受动物的任何子类的列表,方法是说。doSomething(List<Animal> animals)List<Dog>List<Animal>List<Cat>List<Animal>doSomething(List<? extends Animal> animals)

我知道这是Java的行为。我的问题是为什么?为什么多态性通常是隐式的,但是当涉及到泛型时,必须指定它?


答案 1

否,a 不是 .考虑一下你可以用一个做什么 - 你可以添加任何动物到它...包括一只猫。现在,你能合乎逻辑地把一只猫加到一窝小狗身上吗?绝对不行。List<Dog>List<Animal>List<Animal>

// Illegal code - because otherwise life would be Bad
List<Dog> dogs = new ArrayList<Dog>(); // ArrayList implements List
List<Animal> animals = dogs; // Awooga awooga
animals.add(new Cat());
Dog dog = dogs.get(0); // This should be safe, right?

突然间,你有一只非常困惑的猫。

现在,您无法将 a 添加到 ,因为您不知道它是 .您可以检索一个值并知道它将是 一个 ,但您不能添加任意的动物。反之亦然 - 在这种情况下,您可以安全地向其添加 ,但是您对从中检索到的内容一无所知,因为它可能是 .CatList<? extends Animal>List<Cat>AnimalList<? super Animal>AnimalList<Object>


答案 2

您要查找的内容称为协变类型参数。这意味着,如果一种类型的对象可以在方法中替换为另一种类型的对象(例如,可以替换为 ),则同样适用于使用这些对象的表达式(因此可以替换为 )。问题在于,一般来说,协方差对于可变列表是不安全的。假设您有 一个 ,并且它被用作 .当你试图添加一只猫时会发生什么,这真的是一只猫?自动允许类型参数为协变会破坏类型系统。AnimalDogList<Animal>List<Dog>List<Dog>List<Animal>List<Animal>List<Dog>

添加语法以允许将类型参数指定为协变会很有用,这避免了 in 方法声明,但这确实增加了额外的复杂性。? extends Foo


推荐