Java泛型的协变与逆变 |
您所在的位置:网站首页 › java协变与逆变 › Java泛型的协变与逆变 |
导读 泛型是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 |