注意 Java 中数组和范型的协变性(Covariant)

数组的协变性

如果类 Base 是类 Derived 的父类,那么 Base[] 就是 Derived[] 的父类。

因此,我们的代码可以这么写:

1
2
3
4
5
6
7
8
9
Number[] array1 = new Number[2];
array1[0] = Integer.valueOf(1);
array1[1] = Integer.valueOf(2);
System.out.println(array1[0]); // ok

Number[] array2 = new Integer[2];
array2[0] = Integer.valueOf(1);
array2[1] = Integer.valueOf(2);
System.out.println(array2[0]); // ok

但是,数组的协变性可能会导致一些难以发现的错误,比如下面的代码:

1
2
3
/* Error A */
Object[] array3 = new String[10];
array3[0] = 10; // -> array[0] = Integer.valueOf(10)

Error A 可以通过编译,Object[] 类型的引用(注意左边是引用)可以指向一个 String[] 类型的对象。但是,这在运行的时候会报错:

1
Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer
1
2
3
/* Error B */
Number[] array4 = new Number[2];
array4[0] = "abc";

Error B这种错误可以在编译的时候出现。

范型类不具有协变性

最后,范型类 不具有协变性,如 List<Base> 不会是 List<Derived> 的父类。

1
2
3
List<Object> list = new ArrayList<String>();  // 在这里直接凉了
list.add(10);
List<Object> list2 = new ArrayList<Integer>(); // 一起凉了

为什么范型是不协变的?

因为这样做会破坏要提供的类型的安全范型。如果能够将 List<Integer> 赋值给 List<Number>,那么下面的代码将允许将非 Integer 的内容放入 List<Integer>

1
2
3
4
List<Integer> intList = new ArrayList<Integer>();
List<Number> numList = intList; // error
/* Type mismatch: cannot convert from List<Integer> to List<Number> */
numList.add(new Float(1.23));

numList 实际上引用的是一个 List<Integer>,只能存 Integer 或者其 子类,所以在第二行就禁止了这种赋值行为。List<Integer>List<Number> 两者是不同的类,且没有继承关系。

如果需要这样的赋值,比如作为函数的参数,可以这么做:

1
2
List<Integer> intList = new ArrayList<Integer>();
List<? extends Number> numList = intList; // 使用通配符 ?

为什么数组不支持范型?

假设数组支持范型,数组是在运行时才去判断数组元素的类型约束(此时范型信息已被擦除),因为无法做类型约束。

参考