协慌网

登录 贡献 社区

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

我对 Java 泛型如何处理继承 / 多态感到困惑。

假设以下层次结构 -

动物 (父母)

- (儿童)

因此,假设我有一个方法doSomething(List<Animal> animals) 。根据继承和多态性的所有规则,我将假设List<Dog> List<Animal>List<Cat> List<Animal> - 因此可以将任何一个传递给此方法。不是这样如果要实现此行为,则必须通过说doSomething(List<? extends Animal> animals)来明确告诉该方法接受 Animal 的任何子类的doSomething(List<? extends Animal> animals)

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

答案

不, 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?

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

现在,您不能Cat添加到List<? extends Animal>因为您不知道它是List<Cat> 。您可以检索一个值并知道它将是Animal ,但是不能添加任意动物。相反,对于List<? super Animal> - 在那种情况下,您可以安全地向其中添加Animal ,但是您可能对它不了解任何东西,因为它可能是List<Object>

您正在寻找的被称为协变类型参数 。这意味着,如果一种对象类型可以在方法中替代另一种类型(例如, Animal可以用Dog替换),则同样适用于使用这些对象的表达式(因此List<Animal>可以替换为List<Dog> )。问题在于,协方差通常对于可变列表并不安全。假设您有一个List<Dog> ,并且它被用作List<Animal> 。当您尝试将猫添加到此List<Animal> (实际上是List<Dog>什么?自动允许类型参数协变会破坏类型系统。

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

List<Dog>不是List<Animal>是,例如,您可以将Cat插入List<Animal> ,但不能插入List<Dog> ... 您可以使用通配符泛型在可能的情况下更可扩展;例如,从List<Dog>读取与从List<Animal>读取相似,但是不能写入。

Java 语言中泛型和 Java 教程中的泛型部分对为什么某些东西是多态的或不允许使用泛型的,进行了很好的深入解释。