【学习】Java泛型之协变与逆变
https://dzone.com/articles/covariance-and-contravariance
1. 概念引入:举例说明什么是协变
Java中的数组是协变的,也就是:假设 B 继承于 A,那么A[] 可以保存A类型的对象或者B的对象;并且 B[] 也是 A[] 的子类。
Number[] numbers = new Number[3];
numbers[0] = new Integer(10);
numbers[1] = new Double(3.14);
numbers[2] = new Byte(0);
Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;
但是!这样有个问题:将一个double存入int型数组仍然可以通过编译,直到运行时才报错。对于程序来说,留下了隐患。
myNumber [0] = 3.14
注:数组称为”可具体化的类型“(reifiable type),指java运行时系统知道该数组被初始化为整型数组,只是用Number[]引用而已。
2. Java泛型问题 -- 影响了多态的能力
泛型不是可具体化的(non-reifiable),也就是运行时不知道数据的类型信息,这样就可能造成堆污染(heap pollution)——添加不一致的类型对象,会在编译阶段就阻止。
List<Integer> myInts = new ArrayList<Integer>();
myInts.add(1);
myInts.add(2);
List<Number> myNums = myInts; //compiler error, 原因就是运行时不能防止堆污染,先由编译器“拒绝”
myNums.add(3.14); //heap polution
进一步:不会把整型(Integer)的列表看作数值型(Number)的子类,添进Number列表
static long sum(List<Number> numbers) {
long summation = 0;
for(Number number : numbers) {
summation += number.longValue();
}
return summation;
}
List<Integer> myInts = asList(1,2,3,4,5);
System.out.println(sum(myInts)); //compiler error
3. 解决办法:Java泛型的协变(Covariance)和逆变(Contravariance)
两种通配符形式:
3.1 协变 -- 子类型限定,? extends T
表示T的子类型,对于它来说,因为无法确定实际存放的是T的哪一个子类,所以不支持“存放”数据;
但可以作为“获取”数据的类型,因为数据肯定都是T及其子类类型的,超类可以正常引用,维持了多态。
3.2 逆变 -- 超类型限定,? super T
表示存放的是T的超类型,相当于Object类型,也就是,可以存放T及其子类;
但是不能“获取”数据,因为无法确定所取得的数据具体是哪种类型。
试验过:List<? super Number> 可以同时存放Integer和Double类型的数据。
理解:
协变/逆变的概念不太好理解,好像还是一个通用的概念,目前只能结合Java泛型这个具体的例子对应上名称而已。而理解PECS(Producer extends Consumer super),我想到了继承树(图)
如果协变的话,面向树叶,存在分支,不确定具体的类,不能“存放/持有”。而逆变,面向树根,因为Java属于单继承,可以最终找到唯一的超类——Object,而所有的对象都是它的子类,所以可以“存放”。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下