Java 为什么数组是协变的,而泛型是不变的?
摘自Joshua Bloch的《有效Java》Java 为什么数组是协变的,而泛型是不变的?,java,arrays,generics,language-design,covariance,Java,Arrays,Generics,Language Design,Covariance,摘自Joshua Bloch的《有效Java》 数组在两个重要方面不同于泛型类型。第一个数组是协变的。泛型是不变的 协变仅仅意味着如果X是Y的子类型,那么X[]也将是Y[]的子类型。数组是协变的,因为字符串是对象的子类型,所以 String[]是对象[]的子类型 不变量只是指无论X是否为Y的子类型 List<X> will not be subType of List<Y>. 列表将不是列表的子类型。 我的问题是为什么在Java中决定让数组协变?还有其他一些SO帖子
String[]是对象[]的子类型
不变量只是指无论X是否为Y的子类型
List<X> will not be subType of List<Y>.
列表将不是列表的子类型。
Number[]
,您可以在调用Number[]
的地方自由传递或分配一个Integer[]
。(更正式地说,如果Number是整数的超类型,那么Number[]
是Integer[]
的超类型)您可能认为泛型类型也是如此,List
是List
的超类型,您可以在需要List
的地方传递List
。不幸的是,它不是那样工作的
事实证明,有一个很好的理由表明它不能以这种方式工作:它会破坏泛型应该提供的类型安全性。想象一下,您可以将列表
分配给列表
。
然后,以下代码将允许您将非整数的内容放入列表中
:
List li=new ArrayList();
列表ln=li;//非法的
增加(新浮动(3.1415));
因为ln是一个列表
,所以在其中添加浮点数似乎是完全合法的。但是,如果ln的别名为li
,那么它将打破li定义中隐含的类型安全承诺——它是一个整数列表,这就是泛型类型不能协变的原因。通过:
Java和C#的早期版本不包括泛型(又称参数多态性)
在这样的设置中,使数组不变排除了有用的多态程序。
例如,考虑编写一个函数来拖曳一个数组,或者使用一个函数来测试两个数组,以便使用<代码>对象。实现不依赖于存储在数组中的元素的确切类型,因此应该可以编写一个适用于所有类型数组的函数。类型的函数很容易实现
boolean equalArrays (Object[] a1, Object[] a2);
void shuffleArray(Object[] a);
但是,如果数组类型被视为不变量,则只能在类型正好为Object[]
的数组上调用这些函数。例如,不能洗牌字符串数组
因此,Java和C#都以协变的方式处理数组类型。例如,在C#string[]
中是object[]
的子类型,而在Javastring[]
中是object[]
的子类型
这就回答了“为什么阵列是协变的?”,或者更准确地说,“为什么阵列在当时是协变的?”
当引入泛型时,出于下列原因,它们故意不协变:
否,列表
不是使协方差(和反方差)表达式成为可能的列表,例如:
boolean equalLists(List<?> l1, List<?> l2);
void shuffleList(List<?> l);
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
布尔等式列表(列表l1、列表l2);
无效随机列表(列表l);
原因是每个数组在运行时都知道其元素类型,而泛型集合则不知道,因为类型擦除
例如:
boolean equalLists(List<?> l1, List<?> l2);
void shuffleList(List<?> l);
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
数组是协变的,至少有两个原因:
- 它对于保存信息的集合非常有用,这些信息永远不会变为协变信息。对于协变的变量集合,其支持存储也必须是协变的。虽然可以设计一个不可变的
T
集合,而该集合不使用T[]
作为其备份存储(例如,使用树或链表),但这样的集合不太可能像数组支持的集合那样执行。有人可能会说,提供协变不可变集合的更好方法是定义一个“协变不可变数组”类型,它们可以使用后备存储,但简单地允许数组协变可能更容易
- 数组经常会被代码变异,这些代码不知道数组中将包含什么类型的内容,但不会将未从同一数组中读取的任何内容放入数组中。这方面的一个主要例子是排序代码。从概念上讲,数组类型可能包括交换或置换元素的方法(此类方法同样适用于任何数组类型),或者定义一个“数组操纵器”对象,该对象包含对数组的引用以及从中读取的一个或多个内容,并且可以包括将以前读取的项存储到它们所来自的数组中的方法。如果数组不是协变的,用户代码将无法定义这样的类型,但是运行时可能包含一些专门的方法
数组是协变的这一事实可能被视为一个丑陋的黑客行为,但在大多数情况下,它有助于创建工作代码。我的观点:当代码需要一个数组A[]而你给它B[],其中B是A的子类时,只需要担心两件事:读取数组元素时会发生什么,如果你写下来会发生什么。因此,编写语言规则以确保在所有情况下都能保持类型安全并不困难(主要规则是,如果您试图将A插入B[],则可能会抛出ArrayStoreException
)。但是,对于泛型,当您声明一个类SomeClass
时
String first = strings.get(0); // would cause ClassCastException, trying to assign 12 to String
<E extends Comparable<E>> void sort(E[]);
void sort(Comparable[]);
String[] strings = {"hello"};
Object[] objects = strings;
objects[0] = 1; // throws ArrayStoreException
Integer[] numbers = { new Integer(42)};
Object[] objects = numbers;
objects[0] = "forty two"; // throws ArrayStoreException