为什么Java泛型不是隐式多态的?

Is List<Dog> a subclass of List<Animal>? Why are Java generics not implicitly polymorphic?

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

采用以下层次结构-

动物(亲本)

狗-猫(儿童)

所以假设我有一个方法doSomething(List animals)。根据遗传和多态性的所有规则,我假设一个List是一个List,一个List是一个List,因此任何一个都可以传递给这个方法。不是这样。如果我想实现这种行为,我必须明确地告诉这个方法,通过说doSomething(List animals)来接受动物的任何子类的列表。

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


不,List不是List。考虑一下你可以用一个List做什么-你可以在它上面添加任何动物…包括一只猫。现在,你能合理地在一窝小狗中加一只猫吗?绝对不是。

1
2
3
4
5
// 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中,因为您不知道它是List。您可以检索一个值并知道它将是一个Animal,但不能添加任意的动物。相反,对于List—在这种情况下,您可以安全地向它添加Animal,但您不知道从中可以检索到什么,因为它可能是List


您要查找的是所谓的协变类型参数。这意味着,如果一种方法中的一种对象可以替换另一种对象(例如,Animal可以替换为Dog,使用这些对象的表达式也同样适用(因此,List可以替换为List)。问题是,一般来说,协方差对于可变列表是不安全的。假设您有一个List,它被用作List。当你试图在这个List中添加一只猫,而这个List实际上是一个List时会发生什么?自动允许类型参数协变会破坏类型系统。

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