协变、逆变

定义

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. KISS原则

    Keep It Simple, Stupid 1. 模块性原则:写简单的,通过干净的接口可被连接的部件:2. 清楚原则:清楚要比小聪明好.3. 合并原则:设计能被其它程序连接的程序.4. 分离原则:从 ...

  2. MySQL高级查询与编程笔记 • 【第4章 MySQL编程】

    全部章节   >>>> 本章目录 4.1 用户自定义变量 4.1.1 用户会话变量 4.1.2 用户会话变量赋值 4.1.3 重置命令结束标记 4.1.4 实践练习 4.2 存 ...

  3. Selenium_POM架构(17)

    POM是Page Object Model的简称,它是一种设计思想,意思是,把每一个页面,当做一个对象,页面的元素和元素之间操作方法就是页面对象的属性和行为. POM一般使用三层架构,分别为:基础封装 ...

  4. 您应该知道的35个绝对重要的Linux命令

    https://mp.weixin.qq.com/s?__biz=MzU3NTgyODQ1Nw==&mid=2247499293&idx=2&sn=1353b78d6ad01d ...

  5. Module 4 - Azure SQL

    1)     Migrate AdventureWorks database from SQL Server instance to Azure SQL using DMA.2)     Update ...

  6. test_3 简单密码破解

    题目描述:密码是我们生活中非常重要的东东,我们的那么一点不能说的秘密就全靠它了.哇哈哈. 接下来渊子要在密码之上再加一套密码,虽然简单但也安全. 假设渊子原来一个BBS上的密码为zvbo9441987 ...

  7. Python与Javascript相互调用超详细讲解(2022年1月最新)(一)基本原理 Part 1 - 通过子进程和进程间通信(IPC)

    TL; DR 适用于: python和javascript的runtime(基本特指cpython[不是cython!]和Node.js)都装好了 副语言用了一些复杂的包(例如python用了nump ...

  8. 01-JS中字面量与变量

    01-JS中字面量与变量 一.直接量(字面量) 字面量:英语叫做literals,也做直接量,看见什么,它就是什么. (一)数字的字面量 数字的字面量,就是这个数字自己,并不需要任何的符号来界定这个数 ...

  9. ROS Kinetic 使用PocketSphinx进行语音识别报错

     已解决,重启电脑,装环境把声卡驱动那些似乎死机了 ROS Kinetic 使用PocketSphinx进行语音识别教程 https://www.ncnynl.com/archives/201701/ ...

  10. Vue.use()用法

    通常我们引入一个第三方组件形式的插件进来时,我们在main.js里面需要Vue.use('该插件名字'),比如引入一个vant组件 那么我们如何自己也来尝试将自己封装的组件以Vue.use()的形式来 ...