我对Java泛型如何处理继承/多态性有点困惑。
假设以下层次结构-
动物(父母)
狗-猫(儿童)
所以假设我有一个doSomething方法(列出<Animal>动物)。根据继承和多态性的所有规则,我会假设List<Dog>是List<Animal>,List<Cat>是List<Animal>-因此任何一个都可以传递给这个方法。不是这样。如果我想实现这种行为,我必须通过说doSomething(list<?extendsAnimal>动物)来明确告诉方法接受Animal的任何子类的列表。
我知道这是Java的行为。我的问题是为什么?为什么多态性通常是隐式的,但当涉及泛型时,必须指定它?
不,列表<狗>不是列表<动物>。考虑一下你可以用列表<动物>做什么——你可以在其中添加任何动物……包括一只猫。现在,你能在一窝小狗中加入一只猫吗?绝对不是。
// 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?
突然你有一只非常困惑的猫。
现在,您不能将猫添加到列表<?扩展Animal>,因为你不知道它是List<Cat>。您可以检索一个值并知道它将是一个Animal,但不能添加任意的Animal。列表<?super Animal>-在这种情况下,您可以安全地将Animal添加到其中,但您不知道可能从中检索到什么,因为它可能是List<Object>。
为了理解这个问题,比较数组是很有用的。
List<Dog>不是List<Animal>的子类。但狗[]是动物[]的子类。
数组是可具体化和协变的。可重用意味着它们的类型信息在运行时完全可用。因此,数组提供运行时类型安全性,但不提供编译时类型安全。
// All compiles but throws ArrayStoreException at runtime at last line
Dog[] dogs = new Dog[10];
Animal[] animals = dogs; // compiles
animals[0] = new Cat(); // throws ArrayStoreException at runtime
对于泛型,情况也是如此:泛型被删除且不变。因此,泛型不能提供运行时类型安全,但它们提供编译时类型安全。在下面的代码中,如果泛型是协变的,则可能在第3行造成堆污染。
List<Dog> dogs = new ArrayList<>();
List<Animal> animals = dogs; // compile-time error, otherwise heap pollution
animals.add(new Cat());