问题
关于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 Language和Section on Generics from the Java Tutorials有一个非常好的,深入的解释,为什么有些东西是多态的还是非泛化的。