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

23 浏览
0 Comments

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

这是来自Joshua Bloch的《Effective Java》一书中的内容,

  1. 数组与泛型在两个重要的方面存在差异。首先,数组是协变的。而泛型是不变的。
  2. 协变就是如果X是Y的子类型,那么X[]也将是Y[]的子类型。数组是协变的。由于String是Object的子类型,因此

    String[]是Object[]的子类型

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

     List will not be subType of List.
    

我的问题是为什么要在Java中将数组协变?还有其他的SO帖子,如<ahref=\"https://stackoverflow.com/questions/6684493/why-are-arrays-invariant-but-lists-covariant\">为什么数组是不变的,但列表是协变的?,但它们似乎聚焦于Scala而我无法理解。

admin 更改状态以发布 2023年5月21日
0
0 Comments

原因是每个数组在运行时都知道它的元素类型,而通用集合没有因为类型擦除而不知道。

例如:

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

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

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

0
0 Comments

来自维基百科

早期的Java和C#没有包括范型(即参数多态性)。

在这样的情况下,使数组不变会规定出有用的多态程序。例如,考虑编写一个对数组进行打乱的函数,或一个使用Object.equals方法检测两个数组是否相等的函数。实现不依赖于存储在数组中的确切元素类型,因此应该能够编写一个可以在所有类型的数组上工作的单个函数。容易实现类型形式的函数

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

但是,如果将数组类型视为不变,则仅能在完全类型Object[]的数组上调用这些函数,例如不能打乱字符串的数组。

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

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

当范型引入时,它们故意不被设为协变,因为在Jon Skeet的这个答案中指出的原因:

不,List不是一个List。考虑一下你可以对List做什么——你可以添加任何动物到其中……包括一只猫。现在,你逻辑上可以将一只猫添加到一窝小狗中吗?绝对不行。

// Illegal code - because otherwise life would be Bad
List dogs = new List();
List 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);

0