
《Effective Java》 一书有这么一段描述:
- 数组与泛型有两个重要的不同点。首先数组是
covariant, 而泛型是invariantcovariant变量意味着如果X是Y的子类型,那么X[]将也是Y[]的子类型。数组是
covariant,String是Object的子类型,所以
String[] is subtype of Object[]
invariant意味着与X是否是Y的子类无关。
List<X> will not be subType of List<Y>.
引用自 wikipedia :
Java和C#的早期版本是没有泛型(又叫参数化多态)的。这种情况下,让数组是
invariant不利于支持多态的程序。例如,假设你要实现一个打乱数组的方法, 或者实现基于Object.equals测试两个数组元素是否相等的方法。实现要不依赖于数组中存储的具体数据类型,这很容易用一个函数实现接收所有数据类型数组。
boolean equalArrays (Object[] a1, Object[] a2);void shuffleArray(Object[] a);然而,如果数组类型是
invariant, 那调用上面方法的入参只能是Object[]类型。
当后来泛型被引入的时候,没有让泛型和数组一样是 covariant 的原因, stack overflow 上有个好的回答:
List<Dog>不是List<Animal>的子类。假设你声明了一个List<Animal>变量,你能够往里面添加动物,包括阿猫阿狗。但是逻辑上来讲,你能把猫🐱添加到一窝小狗🐶里面嘛,当然是不可以的。
// Illegal codeList<Dog> dogs = new List<Dog>();List<Animal> animals = dogs; //假设是有效的, 实际因为泛型是 invariant, 会编译错误animals.add(new Cat()); // 运行时能正常添加Dog dog = dogs.get(0); // ❌获取的时候这里会转换错误,cause ClassCastException
数组的类似情况:
String[] strings = new String[2];Object[] objects = strings; // ✅, String[] is subtype of Object[]objects[0] = 12; // ❌, would cause java.lang.ArrayStoreException: java.lang.Integer during runtime
为什么数组在运行时添加的时候也会报错,依然设计成 covariant ?
:::info
虽然上面泛型(代码假设是 covariant )和数组都编译期无法发现错误,要运行期才报错。但略有不同,covariant 的泛型 , 错误会延迟到调用者取数据的时候,而数组则是在添加数据的时候就报错。
数组是 covariant , 尽管在添加时会有问题,但是相对于上面提到的结合多态带来的好处,还是可以接受。
泛型如果是 covariant , 那么将错误延迟到使用方才暴露,问题显然更大。
:::
wikipedia 中对数组是 covariant 的设计目的描述不适用于泛型,泛型被设计成 invariant , 因为泛型通配符可以达到 covariance 、 contravariance 的效果,
boolean equalLists(List<?> l1, List<?> l2);void shuffleList(List<?> l);List<String> string1 = new ArrayList<String>();List<String> string2 = new ArrayList<String>();List<?> str = string2; //✅equalLists(string1, string2); //✅
