Loading

型变在 Java 中的体现

PECS 原则:Producer Extends Consumer Super

如果需要取值, 应使用 ? extends T 作为数据结构泛型。
如果需要写值, 应使用 ? super T 作为数据结构泛型。

// java.util.Collections#copy
public static <T> void copy(List<? super T> dest, List<? extends T> src)

在 Java 中,数组的类型只支持协变,不支持逆变,而列表的泛型是不可变的。

Number[] nums = new Integer[10];  // ok
List<Number> numList = new ArrayList<Integer>(); // compile failed

数组支持协变因此在写操作时存在一些问题:

Number[] nums = new Integer[10];
nums[0] = 0;
nums[1] = 1.1;  // compile success

上面这段代码可以通过编译, 但是在运行时将抛出 ArrayStoreException, 因为一个 Integer 的数组中无法存放 Double 类型的元素,因此对支持协变的类型进行写操作是不安全的。

在 Java 中泛型自身是不支持型变的, 所以对于泛型来说不存在这个问题,而型变又是十分常用的特性, 借助型变可以帮助我们编写更加通用的代码。所以 Java 中支持在泛型中使用通配符(上界和下界)来实现泛型的协变和逆变。

List<?> 等同于 List<? extends Object>

List<? extends Number> numList = new ArrayList<Integer>();
List<? super Integer> intList = new ArrayList<Number>();

通过前面数组协变的例子可以得知: 对于协变来说写操作是不安全的, 同理: 对于逆变来说读操作是不确定的(对于使用泛型下界的例子来说, 我们无法确定从中获取到的数据的具体类型), 因此 Java 在编译器层面就禁止对使用泛型上界的对象进行写入操作, 从而保证了协变下的安全性, 而对于使用泛型下界的对象进行读取操作将返回 Object 类型。

posted @ 2022-12-21 15:32  xtyuns  阅读(53)  评论(0编辑  收藏  举报