Java协变、逆变、类型擦除
协变、逆变
定义
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协变、逆变、类型擦除的更多相关文章
- java协变逆变,PECS
public static void main(String[] args) { // Object <- Fruit <- Apple <- RedApple System.out ...
- 解读经典《C#高级编程》最全泛型协变逆变解读 页127-131.章4
前言 本篇继续讲解泛型.上一篇讲解了泛型类的定义细节.本篇继续讲解泛型接口. 泛型接口 使用泛型可定义接口,即在接口中定义的方法可以带泛型参数.然后由继承接口的类实现泛型方法.用法和继承泛型类基本没有 ...
- 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 ...
- C#的in/out关键字与协变逆变
C#提供了一组关键字in&out,在泛型接口和泛型委托中,若不使用关键字修饰类型参数T,则该类型参数是不可变的(即不允许协变/逆变转换),若使用in修饰类型参数T,保证"只将T用于输 ...
- java为什么要用类型擦除实现泛型?--c++,java,c# 的泛型是如何实现的
所以总结一下c++,java,c#的泛型.c++的泛型在编译时完全展开,类型精度高,共享代码差.java的泛型使用类型擦出,仅在编译时做类型检查,在运行时擦出,共享代码好,但是类型精度不行.c#的泛型 ...
- Programming In Scala笔记-第十九章、类型参数,协变逆变,上界下界
本章主要讲Scala中的类型参数化.本章主要分成三个部分,第一部分实现一个函数式队列的数据结构,第二部分实现该结构的内部细节,最后一个部分解释其中的关键知识点.接下来的实例中将该函数式队列命名为Que ...
- JAVA泛型——逆变
在上篇<JAVA泛型——协变>这篇文章中遗留以下问题——协变不能解决将子类型添加到父类型的泛型列表中.本篇将用逆变来解决这个问题. 实验准备 我们首先增加以下方法,见代码清单1所示. 代码 ...
- Java泛型-内部原理: 类型擦除以及类型擦除带来的问题
一:Java泛型的实现方法:类型擦除 大家都知道,Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除.Java的泛型基本上都是在编译 ...
- java泛型总结(类型擦除、伪泛型、陷阱)
JDK1.5开始实现了对泛型的支持,但是java对泛型支持的底层实现采用的是类型擦除的方式,这是一种伪泛型.这种实现方式虽然可用但有其缺陷. <Thinking in Java>的作者 B ...
- [改善Java代码]Java的泛型是类型擦除的
泛型可以减少强制类型的转换,可规范集合的元素类型,还可以提高代码的安全性和可读性,正是因为有了这些优点,自从Java引入泛型之后,项目的编码规则上便多了一条,优先使用泛型. Java泛型(Generi ...
随机推荐
- css基础-2 div布局
div布局 <html> <head> <title>div布局 </title> <meta charset="utf-8" ...
- css 基础 css引入方式
color:red; //改变颜色 font-size:18px : //改变文字大小 background-color : blue; //设置背景颜色 width:100px ://设置宽度 he ...
- Docker_使用Rancher管理docker(7)
一.简介 Rancher是一个开源的企业级全栈化容器部署及管理平台.Rancher为容器提供一揽子基础架构服务:CNI兼容的网络服务.存储服务.主机管理.负载均衡.防护墙--Rancher让上述服务跨 ...
- 初识python 之 mysql数据库基本操作
import pymysql 注:所有插入.修改.删除操作都必须要提交(conn.commit()) 连接数据库: conn = pymysql.connect( host = '127.0.0.1' ...
- spring boot 热部署 实现 前端部分热更新 详细操作
1.前言 在以前的随笔[https://www.cnblogs.com/c2g5201314/p/12275243.html] 里面已经讲解过了 idea 如何在 springMVC 项目 实现 前端 ...
- linux笔记(一)
linux 开源镜像网址:http://mirrors.163.com pwd : 展示当前所在的目录的绝对路径 cd : 切换到某个路径 cd 命令,是 Change Directory 的缩写, ...
- 关于APP设计规范和一些图层命名
首先,本人大学计算机专业出身,学过编程,工作的时候做过 产品经理,设计师,前端工程师,对工作的流程都有一些见解. 现在主攻前端工程师,做Web APP.今天收到设计师的设计稿,一看图层分类,这让我感觉 ...
- 第51篇-SharedRuntime::generate_native_wrapper()生成编译入口
当某个native方法被调用时,一开始它会从解释入口进入,也就是我之前介绍的.由InterpreterGenerator::generate_native_entry()函数生成的入口例程.在这个例程 ...
- Docker之Docker Machine已弃用
作为刚开始学习docker的新手,从网上查询文档无疑是最快的学习捷径,但是这次在docker翻车了,困扰了整整三天,特记录一下. 一般我们使用docker for windows安装,一路往下点直至安 ...
- idea同时启动多个微服务模块进行管理
1,打开IDEA项目中的 .idea 下 的workspace.xml 找到文件中的 RunDashboard 配置块,增加如下圈起来的地方 代码: <option name="con ...