为什么数组是协变的,而泛型是不变的?
为什么数组是协变的,而泛型是不变的?
这是来自Joshua Bloch的《Effective Java》一书中的内容,
- 数组与泛型在两个重要的方面存在差异。首先,数组是协变的。而泛型是不变的。
- 协变就是如果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而我无法理解。
原因是每个数组在运行时都知道它的元素类型,而通用集合没有因为类型擦除而不知道。
例如:
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
如果通用集合允许这样:
Liststrings = new ArrayList (); List
但是,当有人尝试访问列表时,这会引起问题:
String first = strings.get(0); // would cause ClassCastException, trying to assign 12 to String
来自维基百科:
早期的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 Listdogs = 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);