image.png

    《Effective Java》 一书有这么一段描述:

    1. 数组与泛型有两个重要的不同点。首先数组是 covariant , 而泛型是 invariant
    2. covariant 变量意味着如果 XY 的子类型,那么 X[] 将也是 Y[] 的子类型。

    数组是 covariant , StringObject 的子类型,所以

    1. String[] is subtype of Object[]

    invariant 意味着与 X 是否是 Y 的子类无关。

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

    引用自 wikipedia :

    JavaC# 的早期版本是没有泛型(又叫参数化多态)的。

    这种情况下,让数组是 invariant 不利于支持多态的程序。例如,假设你要实现一个打乱数组的方法, 或者实现基于 Object.equals 测试两个数组元素是否相等的方法。实现要不依赖于数组中存储的具体数据类型,这很容易用一个函数实现接收所有数据类型数组。

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

    然而,如果数组类型是 invariant , 那调用上面方法的入参只能是 Object[] 类型。

    当后来泛型被引入的时候,没有让泛型和数组一样是 covariant 的原因, stack overflow 上有个好的回答

    List<Dog> 不是 List<Animal> 的子类。假设你声明了一个 List<Animal> 变量,你能够往里面添加动物,包括阿猫阿狗。但是逻辑上来讲,你能把猫🐱添加到一窝小狗🐶里面嘛,当然是不可以的。

    1. // Illegal code
    2. List<Dog> dogs = new List<Dog>();
    3. List<Animal> animals = dogs; //假设是有效的, 实际因为泛型是 invariant, 会编译错误
    4. animals.add(new Cat()); // 运行时能正常添加
    5. Dog dog = dogs.get(0); // ❌获取的时候这里会转换错误,cause ClassCastException

    数组的类似情况:

    1. String[] strings = new String[2];
    2. Object[] objects = strings; // ✅, String[] is subtype of Object[]
    3. objects[0] = 12; // ❌, would cause java.lang.ArrayStoreException: java.lang.Integer during runtime

    为什么数组在运行时添加的时候也会报错,依然设计成 covariant ?

    :::info 虽然上面泛型(代码假设是 covariant )和数组都编译期无法发现错误,要运行期才报错。但略有不同,
    covariant 的泛型 , 错误会延迟到调用者取数据的时候,而数组则是在添加数据的时候就报错。

    数组是 covariant , 尽管在添加时会有问题,但是相对于上面提到的结合多态带来的好处,还是可以接受。

    泛型如果是 covariant , 那么将错误延迟到使用方才暴露,问题显然更大。 :::

    wikipedia 中对数组是 covariant 的设计目的描述不适用于泛型,泛型被设计成 invariant , 因为泛型通配符可以达到 covariancecontravariance 的效果,

    1. boolean equalLists(List<?> l1, List<?> l2);
    2. void shuffleList(List<?> l);
    3. List<String> string1 = new ArrayList<String>();
    4. List<String> string2 = new ArrayList<String>();
    5. List<?> str = string2; //✅
    6. equalLists(string1, string2); //✅