【学习】Java泛型之协变与逆变

【学习】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,而所有的对象都是它的子类,所以可以“存放”。

 

posted @   eternalFlame  阅读(543)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示