协变、逆变

定义

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. vue是如何通过diff算法做渲染更新

    渲染页面 图中框起来的部分,vue会根据响应式变化的通知生成一颗新的 Virtual Dom Tree,然后将新的Virtual Dom Tree 和之前的 Virtual Dom Tree 做 di ...

  2. Swoole 协程的并发调用及使用示例

    示例一: 利用通道pop会自动挂起当前协程,等待生产者推送数据的特性,实现并发调用,并在协程完成后组合结果集. $serv = new Swoole\Http\Server("127.0.0 ...

  3. MongoDB_安装、配置、连接(五)

    MongoDB 是跨平台的,既可以在 Linux系统下安装,也可以在Windows 系统.MacOS系统下安装,本节主要介绍如何在 Linux 系统下安装 MongoDB. windows安装:htt ...

  4. 初识python 之 MongoDB 基本操作

    MongoDB与SQL对比: MongoDB 三元素:数据库.集合.文档 MongoDB 基本操作命令: db 查看当前数据库 show dbs 查看所有数据库 use 数据库名 切换数据库,如果数据 ...

  5. RabbitMQ 消息中间件 的下载与安装【window10】

    1.前言 弄了好久,才终于把 rabbitmq装好 ,本来是很简单的,但是,安装有个要求就是路径不能有中文字符, 虽然可以安装,但是无法运行,需要修改路径名为非中文字符后重装rabbitmq才可以运行 ...

  6. Go语言系列之单元测试

    go test工具 Go语言中的测试依赖go test命令.编写测试代码和编写普通的Go代码过程是类似的,并不需要学习新的语法.规则或工具. go test命令是一个按照一定约定和组织的测试代码的驱动 ...

  7. vue3.0+vite+ts项目搭建--初始化项目(一)

    vite 初始化项目 使用npm npm init vite@latest 使用yarn yarn create vite 使用pnpm pnpx create-vite 根据提示输入项目名称,选择v ...

  8. axios发送两次请求问题解决

    在使用axios的过程中,会发送两次请求. 看了下是因为有一个请求是OPTIONS来判断跨域的时候让不让发送请求的. 这个不算是一个bug,但是发送两个请求着实让人看着不舒服.于是修改了下,原来的请求 ...

  9. JAVA-JDK1.7-ConCurrentHashMap-测试和验证

    概述 上次记录了关于ConCurrentHashMap的原理以及源码,现在我对其进行有关功能的测试,下面就讲解一下我测试的内容和代码.这些测试主要针对JDK1.7版本. GET安全测试 上一篇写过ge ...

  10. Java中生成一个唯一值的方式

    现总结几种生成一个唯一值的方式 第一种:采用nanoTime() // 理论上存在重复的可能,可以在后面再加上一个随机字符串 Random r = new Random(); for (int i = ...