我对 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 教程中的泛型部分对为什么某些东西是多态的或不允许使用泛型的,进行了很好的深入解释。