协变、逆变

定义

Java中String类型是继承自Object的,姑且记做String ≦ Object,表示String是Object的子类型,String的对象可以赋给Object的对象。而Object的数组类型Object[],理解成是由Object构造出来的一种新的类型,可以认为是一种构造类型,记f(Object),那么可以这么来描述协变和逆变:

当A ≦ B时,如果有f(A) ≦ f(B),那么f叫做协变(子类赋值给父类);

当A ≦ B时,如果有f(B) ≦ f(A),那么f叫做逆变(父类赋值给子类);

如果上面两种关系都不成立则叫做不可变。

数组协变

代码

    @Test
public void testZero(){
Food food = new Fruit();
// or
food = new Meat(); // 即 把子类赋值给父类引用 Fruit [] arrFruit = new Fruit[3];
Food [] arrFood = new Food[3];
arrFood=arrFruit; // 数组协变,把子类数组赋值给父类数组
//arrFruit=arrFood;//error 不能逆变
}

泛型协变与逆变

泛型

泛型没有内建的协变类型

代码

@Test
public void testOne(){
List<Meat> beefListRoot=new ArrayList<>();
// List<Food> foodList=beefListRoot; //错误:不可协变,即子类list不能赋值给父类list
List<Food> foodListRoot=new ArrayList<>();
// beefListRoot=foodListRoot; //错误 : 不可逆变,即父类list不能赋值给子类list }

我们可以使用通配符实现泛型的协变和逆变

通配符协变

代码

 @Test
public void testTwo(){
List<? extends Food> foodList = new ArrayList<>();
List<Apple> appleList = new ArrayList<>();
foodList = appleList; // ok 协变,即子类list赋值给父类list
// foodList.add(new Apple());//不能执行添加null 以外的操作,原因:反正法:beef也是food子类,但是不该加入苹果列表,否则get时类型转换异常,就有问题
Food food = foodList.get(0); //ok, 把子类引用赋值给父类显然是可以的 }

通配符逆变

代码

   @Test
public void testThree(){
List<? super Fruit> fruitList = new ArrayList<>();
List<Food> foodList = new ArrayList<>();
foodList.add(new Meat()); fruitList = foodList; // ok 逆变,父类列表赋值给子类列表 fruitList.add(new Apple()); // ok,只能添加 Fruit 或者 其子类
// fruitList.add(new Food());// error, 只能添加 Fruit 或者 其子类 //Fruit fruit = fruitList.get(0); // error,get出来的元素是Object类型
Object obj = fruitList.get(0);// ok
}

通配符的协变和逆变使用场景

如果参数化类型表示一个生产者,就使用<? extends T>。比如list.get(0)这种,list作为数据源producer;

如果它表示一个消费者,就使用<? super T>。比如:list.add(new Apple()),list作为数据处理端consumer。

类型擦除

定义

Java的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除。

如在代码中定义List和List等类型,在编译后都会变成List,JVM看到的只是List,而由泛型附加的类型信息对JVM是看不到的。

使用泛型获取返回值之前,泛型变量进行强转。

如:

public E get(int index) {  

    RangeCheck(index);  

    return (E) elementData[index];  

}

相关定义

  • 原始类型

就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型,无论何时定义一个泛型,相应的原始类型都会被自动提供,类型变量擦除,并使用其限定类型(无限定的变量用Object)替换。

如: ArrayList<String> 原始类型为Object;ArrayList<T extend Apple> 原始类型为Apple;ArrayList<T super Apple> 原始类型为Object;

证明泛型擦除的案例

  • 1
    @Test
public void test() {
List<String> ls=new ArrayList<String>();
List<Integer> ln=new ArrayList<Integer>();
//class java.util.ArrayList
System.out.println(ls.getClass());
//class java.util.ArrayList
System.out.println(ln.getClass()); }
  • 2
    @Test
public void testTwo() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
ArrayList<Integer> list=new ArrayList<Integer>();
//获取到list对象的add方法
Method testTwo = list.getClass().getMethod("add",Object.class);
//添加数据,定义泛型为整形,但是反射获取类型后可以进行添加String
testTwo.invoke(list, "wqewqe");
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}

Gitte代码

泛型擦除:https://gitee.com/zhuayng/foundation-study/blob/develop/JavaBasis/Other/src/main/java/com/yxkj/other/modular/wildcard/erase/GenericErase.java

逆变与协变:https://gitee.com/zhuayng/foundation-study/blob/develop/JavaBasis/Other/src/main/java/com/yxkj/other/modular/wildcard/erase/transmute.java

参考

泛型擦除:https://blog.csdn.net/Dcwjh/article/details/102832280?utm_medium=distribute.pc_relevant_t0.none-task-blog-2~default~CTRLIST~default-1.no_search_link&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2~default~CTRLIST~default-1.no_search_link

逆变与协变:https://zhuanlan.zhihu.com/p/131602691;

https://blog.csdn.net/wangnanwlw/article/details/108711962?utm_medium=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~default-1.no_search_link&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~default-1.no_search_link

Java协变、逆变、类型擦除的更多相关文章

  1. java协变逆变,PECS

    public static void main(String[] args) { // Object <- Fruit <- Apple <- RedApple System.out ...

  2. 解读经典《C#高级编程》最全泛型协变逆变解读 页127-131.章4

    前言 本篇继续讲解泛型.上一篇讲解了泛型类的定义细节.本篇继续讲解泛型接口. 泛型接口 使用泛型可定义接口,即在接口中定义的方法可以带泛型参数.然后由继承接口的类实现泛型方法.用法和继承泛型类基本没有 ...

  3. C#中泛型方法与泛型接口 C#泛型接口 List<IAll> arssr = new List<IAll>(); interface IPerson<T> c# List<接口>小技巧 泛型接口协变逆变的几个问题

    http://blog.csdn.net/aladdinty/article/details/3486532 using System; using System.Collections.Generi ...

  4. C#的in/out关键字与协变逆变

    C#提供了一组关键字in&out,在泛型接口和泛型委托中,若不使用关键字修饰类型参数T,则该类型参数是不可变的(即不允许协变/逆变转换),若使用in修饰类型参数T,保证"只将T用于输 ...

  5. java为什么要用类型擦除实现泛型?--c++,java,c# 的泛型是如何实现的

    所以总结一下c++,java,c#的泛型.c++的泛型在编译时完全展开,类型精度高,共享代码差.java的泛型使用类型擦出,仅在编译时做类型检查,在运行时擦出,共享代码好,但是类型精度不行.c#的泛型 ...

  6. Programming In Scala笔记-第十九章、类型参数,协变逆变,上界下界

    本章主要讲Scala中的类型参数化.本章主要分成三个部分,第一部分实现一个函数式队列的数据结构,第二部分实现该结构的内部细节,最后一个部分解释其中的关键知识点.接下来的实例中将该函数式队列命名为Que ...

  7. JAVA泛型——逆变

    在上篇<JAVA泛型——协变>这篇文章中遗留以下问题——协变不能解决将子类型添加到父类型的泛型列表中.本篇将用逆变来解决这个问题. 实验准备 我们首先增加以下方法,见代码清单1所示. 代码 ...

  8. Java泛型-内部原理: 类型擦除以及类型擦除带来的问题

    一:Java泛型的实现方法:类型擦除 大家都知道,Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除.Java的泛型基本上都是在编译 ...

  9. java泛型总结(类型擦除、伪泛型、陷阱)

    JDK1.5开始实现了对泛型的支持,但是java对泛型支持的底层实现采用的是类型擦除的方式,这是一种伪泛型.这种实现方式虽然可用但有其缺陷. <Thinking in Java>的作者 B ...

  10. [改善Java代码]Java的泛型是类型擦除的

    泛型可以减少强制类型的转换,可规范集合的元素类型,还可以提高代码的安全性和可读性,正是因为有了这些优点,自从Java引入泛型之后,项目的编码规则上便多了一条,优先使用泛型. Java泛型(Generi ...

随机推荐

  1. 去除input标签点击时的默认样式

    去除input标签点击时的默认样式的方法 outline:none; //去除点击时的边框 border : none; //去除input框的边框

  2. Generating Adversarial Examples with Adversarial Networks

    目录 概 主要内容 black-box 拓展 Xiao C, Li B, Zhu J, et al. Generating Adversarial Examples with Adversarial ...

  3. html5调用摄像头并拍照

    随着flash被禁用,flash上传附件的方式已成为过去,现在开始用html5上传了.本片文章就是介绍如何使用html5拍照,其实挺简单的原理: 调用摄像头采集视频流,利用canvas的特性生成bas ...

  4. [opencv]拟合vector<Mat>集合区域接近的元素

    vector<Rect> PublicCardFrameDetection::fitrect(vector<Rect> rects){ int size = rects.siz ...

  5. 替代RTD2166|CS5212直接Pin to pin兼容替代RTD2166|替代RTD2166方案

    RTD2166功能概述 RTD2166是一款DisplayPort端口到VGA转换器,成本较高,Capstone于2019年推出CS5212,直接Pin to pin兼容替代RTD2166,可用原RT ...

  6. Java初学者作业——分别计算两个整数加、减、乘、除的结果并显示,要求除法保留两位小数。

    返回本章节 返回作业目录 需求说明: 分别计算两个整数加.减.乘.除的结果并显示,要求除法保留两位小数. 实现思路: 接收用户控制台输入的两个整数. 实现两个整数的加.减.乘.除的运算并输出结果. 除 ...

  7. js 鼠标经过和经过离开 导航实战案例

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  8. 初识python: 模块定义及调用

    一.定义 模块:用来从逻辑上组织python代码(变量.函数.类.逻辑:实现一个功能),本质就是.py结尾的python文件(比如:文件名:test.py,对应的模块名:test) 包:用来从逻辑上组 ...

  9. Could not find resource mybatis.xml 找不到mybatis主配置文件的三种解决方式

    第一种:先清除target目录 再重新compile编译 第二种:让idea重构项目 第三种 :手动添加到target目录下的classes包下

  10. 安霸pipeline简述之YUV域的处理

    YUV域处理模块的详细介绍: YUV域的处理主要是rgb_to_yuv_matrix,chroma_scale,ASF(空域降噪),MCTF(时域降噪),SharpenB(锐化模块). RGB2YUV ...