软件构造——Java中的型变与泛型

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/lll_90/article/details/93357863

型变:当子类型关系出现在更加复杂类型中时,新类型中有着怎样的类型关系?
如:
Cat是Animal的子类,那么List 和List有什么关系?

型变有如下三种情况:
Covariant(协变): 保持子类型关系List是List的子类
Contravariant(逆变):反转了子类型关系 ,如List是List的子类
Invariant(不变): 没有子类型关系,如List nor 和List均不具备父类型与子类型关系
图示是子类型中重写父类型方法,参数和返回值的协变逆变情况:
在这里插入图片描述

Java支持协变,不支持逆变。当发生类似于逆变的情况时,编译器默认子类型与父类型中的方法是重载关系,而不是重写。如下面的例子:
在这里插入图片描述可以得到如下输出:
在这里插入图片描述
上图说明Java编译器不支持逆变,在编译阶段没有将子类型中callAnimal()方法的Object参数类型逆变成父类型的Animal,而是把它们当成参数类型不同处理。
这里,子类型中的callAnimal()方法与父类型中的callAnimal()方法在运行阶段发生了重载。故而根据参数声明的类型不同,动态选择子类型与父类型中名字均为callAnimal()的方法。

Java泛型不支持型变:
在这里插入图片描述
在这里插入图片描述
虚拟机中没有泛型类型对象。泛型信息只存在于编译阶段,在运行时会被”擦除”,并替换为限定类型,如果没有限定类型则为Object类型。

泛型中不能调用具体类型的相关方法,但是list中的contains()会自动调用类型的equals()方法
在限定了T的具体类型后,可以调用类型相关的具体方法。

运行时类型查询只适用于原始类型,
在这里插入图片描述输出
在这里插入图片描述

因为运行时,所有的数据类型均要被擦除,还原为raw type,这样,List中的对象的数据类型也将不复存在。因此,if(cats instanceof List<?>)反倒编译通过。
在这里插入图片描述

具有限定类型下的list操作
List是 List<? extends Animal>的子集
1.对于类型 List<? extends Animal>,调用一个返回Animal类型(或子类型)的方法(如:T get(int pos))是安全的, 因为compiler知道这个List中的任何对象至少具有Animal类型(或子类型),可以完成类型转换。
2.但调用类似add(E e)的方法则不安全,类型擦除机制会导致运行时可以往animals中储存各种类型的对象。因此,Java此时禁止了List中所有具有泛型输入参数的方法,如:add(T item)
在这里插入图片描述注释掉中间的三行add()操作后,
在这里插入图片描述
List是 List<? super Cat>的子集
1.对于List中的 T get(int pos)方法,当指定类型是“? super Cat”时, get方法的返回类型就变成了“? super Cat”, 即返回类型可能是Cat或者Cat的基类型,compiler无法确定具体类型,因此拒绝调用任何返回类型为T的方法(除非是读取为Object类)
2.但调用类似add(E e)的方法则安全, 传入Cat及其子类(WhiteCat)是安全的, 因为compiler知道这个List包含的是Cat或Cat的基类对象。因此,Java此时禁止了List中所有具有泛型返回类型的方法,如:get()
在这里插入图片描述
将编译错误行注释掉,
在这里插入图片描述
可得到输出:
在这里插入图片描述

上述规则可简记为“get-extends, add-super”

通配符下的子类型关系
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/lll_90/article/details/93357863