为什么数组是协变的,而泛型是不变的?

来自Joshua Bloch的 Effective Java,

  1. 数组在两个重要方面与泛型类型不同。第一个数组是协变的。泛型是不变的。
  2. 协变简单地表示如果 X 是 Y 的子类型,则 X[] 也将是 Y[] 的子类型。数组是协变 的 字符串是 Object So 的子类型

    String[] is subtype of Object[]

    不变量只是意味着无论 X 是否是 Y 的子类型,

     List<X> will not be subType of List<Y>.
    

我的问题是,为什么决定在Java中使数组协变?还有其他SO帖子,例如为什么数组是不变的,但列表协变?,但它们似乎集中在Scala上,我无法遵循。


答案 1

通过维基百科

Java和C#的早期版本不包括泛型(又名参数化多态性)。

在这样的设置中,使数组不变会排除有用的多态程序。例如,考虑编写一个函数来随机排列数组,或者编写一个函数来使用元素上的方法测试两个数组的相等性。该实现不依赖于数组中存储的元素的确切类型,因此应该可以编写一个适用于所有类型的数组的函数。易于实现类型的功能Object.equals

boolean equalArrays (Object[] a1, Object[] a2);
void shuffleArray(Object[] a);

但是,如果数组类型被视为不变的,则只能在类型完全相同的数组上调用这些函数。例如,不能对字符串数组进行随机排序。Object[]

因此,Java 和 C# 都以协变方式处理数组类型。例如,在 C# 中是 的子类型,在 Java 中是 的子类型。string[]object[]String[]Object[]

这回答了“为什么数组是协变的?”的问题,或者更准确地说,“为什么数组在当时是协变

当泛型被引入时,由于Jon Skeet在回答中指出的原因,它们故意不成为协变:

否,a 不是 .考虑一下你可以用一个做什么 - 你可以添加任何动物到它...包括一只猫。现在,你能合乎逻辑地把一只猫加到一窝小狗身上吗?绝对不行。List<Dog>List<Animal>List<Animal>

// Illegal code - because otherwise life would be Bad
List<Dog> dogs = new List<Dog>();
List<Animal> animals = dogs; // Awooga awooga
animals.add(new Cat());
Dog dog = dogs.get(0); // This should be safe, right?

突然间,你有一只非常困惑的猫。

维基百科文章中描述的使数组协变的原始动机不适用于泛型,因为通配符使协方差(和逆变)的表达成为可能,例如:

boolean equalLists(List<?> l1, List<?> l2);
void shuffleList(List<?> l);

答案 2

原因是每个数组在运行时都知道其元素类型,而泛型集合不知道,因为类型擦除。

例如:

String[] strings = new String[2];
Object[] objects = strings;  // valid, String[] is Object[]
objects[0] = 12; // error, would cause java.lang.ArrayStoreException: java.lang.Integer during runtime

如果泛型集合允许这样做:

List<String> strings = new ArrayList<String>();
List<Object> objects = strings;  // let's say it is valid
objects.add(12);  // invalid, Integer should not be put into List<String> but there is no information during runtime to catch this

但是,当有人尝试访问列表时,这会导致问题:

String first = strings.get(0); // would cause ClassCastException, trying to assign 12 to String