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

问题

关于Java泛型如何处理继承/多态,我有点困惑。

假设以下层次结构 -

动物(父母)

-Cat(儿童)

所以假设我有一个方法doSomething(List <Animal> animals)。通过所有继承和多态的规则,我会假设List <Dog>isaList <Animal>List <Cat>isaList <Animal> - 所以任何一个都可以通过这个方法。不是这样。如果我想实现这种行为,我必须通过说'doSomething(List <?extends Animal> animals)来明确地告诉方法接受Animal的任何子集的列表。

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


#1 热门回答(744 赞)

不,List <Dog>isnotaList <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 <?扩展Animal>因为你不知道它是一个List <Cat>。你可以检索一个值,并知道它将是一个“动物”,但你不能添加任意动物。对于List <?反之亦然。 super Animal> - 在这种情况下你可以安全地添加一个Animal,但你不知道从它可以检索到什么,因为它可能是一个List <Object>


#2 热门回答(66 赞)

你正在寻找的是covariant type参数。问题是它们在一般情况下不是类型安全的,特别是对于可变列表。假设你有一个List <Dog>,它可以作为List <Animal>运行。当你试图将一个Cat添加到这个List <Animal>这真的是一个List <Dog>时会发生什么?自动允许类型参数是协变的因此打破了类型系统。

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


#3 热门回答(42 赞)

“List <Dog>”不是“List <Animal>”的原因是,例如,你可以将Cat插入List <Animal>,但不能插入List <Dog>...你可以使用通配符在可能的情况下使泛型更具可扩展性;例如,从“List <Dog>”读取类似于从“List <Animal>”读取 - 但不是写作。

Generics in the Java LanguageSection on Generics from the Java Tutorials有一个非常好的,深入的解释,为什么有些东西是多态的还是非泛化的。