Java泛型的协变与逆变

您所在的位置:网站首页 java协变与逆变 Java泛型的协变与逆变

Java泛型的协变与逆变

2024-07-15 13:19| 来源: 网络整理| 查看: 265

导读

泛型是Java最基础的语法之一,众所周知:出于安全原因,泛型默认不能支持型变(否则会引入危险),因此Java提供了通配符上限和通配符下限来支持型变,其中通配符上限就泛型协变,通配符下限就是泛型逆变。

由于来自早期的设计,所以Java的数组默认就支持型变(Variance):只要A是B的子类,那么A[]就相当于B[]的子类,比如Integer是Number的子类,因此Integer[]就相当于Number[]的子类。

但数组的型变会导致潜在的问题,例如如下程序:

代码语言:javascript复制public class ArrayVariance { public static void main(String[] args) { Integer[] intArr = new Integer[5]; // 数组默认就支持型变,因此下面代码是正确的 Number[] numArr = intArr; // numArr只要求集合元素是Number,因此下面代码也可通过编译 numArr[0] = 3.4; // ① } }

由于Java数组默认支持型变,因此Number[]类型的变量实际引用的数组完全可以是Integer[]、也可是Float[]、也可是Double[]......,因此当程序尝试向 Number[]数组中存入元素时,总有可能导致ArrayStoreException异常——原因就是你永远无法确定Number[]类型的变量实际引用的数组对象。

上面程序编译没有任何问题,但运行上面程序就会导致ArrayStoreException异常。

注意

对于一个强大的编译器来说,如果程序在编译阶段没有警告、没有错误 ,那么运行时就不应该导致简单的语法错误——上面程序编译阶段没有错误,但运行时仅仅只是因为类型不兼容(Java是强类型语言)而出错,这显然是不尽人意的。

泛型默认不支持型变

为了避免重蹈Java数组的覆辙,Java泛型显然不能再继续支持默认的型变。这意味着:即使A是B的子类,那么List也不是List的子类,比如Integer是Number的子类,而List却并不是List的子类。

例如如下程序:

代码语言:javascript复制import java.util.*; public class GenericNoVariance { public static void main(String[] args) { List intList = List.of(2, 4); // 泛型默认不支持型变,因此下面代码编译错误 List numList = intList; // ① numList.add(3.4); // ② } }

由于泛型不支持默认的型变,因此上面①号代码在编译阶段就会报错,因此在②号代码就无法在运行时导致错误了。

协变:通配符上限

为了让泛型支持型变,Java引入了通配符上限语法:如果A是B的子类,那么List相当于是List



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3