一、引言

  复习javac的编译过程中的解语法糖的时候看见了泛型擦除中的举例,网上的资料大多比较散各针对性不一,在此做出自己的一些详细且易懂的总结。

二、泛型简介

  泛型是JDK 1.5的一项新特性,一种编译器使用的范式,语法糖的一种,能保证类型安全。【注意:继承中,子类泛型数必须不少于父类泛型数】

  为了方便理解,我将泛型分为普通泛型通配泛型

三、泛型分类

1、普通泛型  

  就是没有设置通配的泛型,泛型表示为某一个类。

  声明时: class Test<T>{...}

  使用时: Test<Integer> test = new Test<Integer>();

  作为无界泛型,其实就是约束左右泛型必须一致。

2、通配泛型

  通配泛型包括两种,无界通配和有界通配。

  【无界通配符】

  <?>通配符——表示所有类型都能与它匹配

  【有界通配符】

  extends(上界)通配符——声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类;

  super(下界)通配符——声明了类型的下界,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至Object。类型擦除后剩下(下面以extends举例)

//有界泛型类型语法 - 继承自某父类
<T extends ClassA>
//有界泛型类型语法 - 实现某接口
<T extends InterfaceB>
//有界泛型类型语法 - 多重边界
<T extends ClassA & InterfaceB & InterfaceC ... > //示例
<N extends Number> //N标识一个泛型类型,其类型只能是Number抽象类的子类
<T extends Number & Comparable & Map> //T标识一个泛型类型,其类型只能是Person类型的子类,并且实现了Comparable 和Map接口

【注意:多重边界里,只允许第一个能为类,后续必须为接口】

四、List<T> 和 List<?> 和 List<Object> 的区别

  类声明的时候采用List<T>,此时T可以为任何字母,都指代普通泛型。

    例如: class Test<T>{...}

  实例化的时候采用List<?>,此时?可以为任何类,表示只能存入此类对象,也可以就写‘<?>’,代表可以存入任何类对象,属于通配泛型。

    例如:List<?> listOfString = new ArrayList<String>;

  但是注意List<?>与List<Object>不一样,前者是所有泛型的通配符,即所有泛型的引用都能与他进行匹配(作为实例化的右边),而Object只是一个单独的类,当为实例化左边的时候,有且仅有为<Object>相匹配(或者不写泛型),例如:List<Object> list = new ArrayList<Object>();,实例化左边的泛型作为“答案范围”,实例化右边的泛型只能为“答案”是某一个类。例如:

List<String> list = new ArrayList<?>(); // 编译错误:通配符是“答案范围”不能作为“答案”出现在实例化的右边
List<?> list = new ArrayList<String>(); // String与?匹配成功
List<? extends Number> list = new ArrayList<? extends Integer>(); // 编译错误:有界泛型同样也是“答案范围”,不能出现在实例化的右边
List<? extends Number> list = new ArrayList<Integer>(); // 右边的"答案"与左边的“答案范围”匹配成功

五、泛型擦除

 由来:一开始java并没有泛型,后来1.5加入了泛型,为了能向前兼容(旧版本的jvm能解释运行新版本的.class文件)所以就采用了伪泛型——“泛型擦除”,并一直保留了下来。

  原理:泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉,擦除后会变成原始类型(去掉<T>,将方法内的T擦除成Object)例如Generic<T>会被擦除成Generic。还需要注意的是,不同的通配符的擦除的方式也有不同:

  口诀:【存入:取下界;取出:取上界】—or—【存下,取上】

  当泛型作为方法的传入参数的时候,此时替换成通配泛型的下界,例如add方法

  当泛型作为方法的返回参数的时候,此时替换成通配泛型的上界,例如get方法

List<? extends Integer> list1 = new ArrayList<Integer>();
list1.add(null); // 此时传入取<? extends Integer> 下界————无 所以只能传null,否则报错
Integer integer1 = list1.get(0); // // 此时返回取<? extends Integer> 上界————Integer List<? super Integer> list2 = new ArrayList<Integer>();
list2.add(111); // 此时传入取<? super Integer> 下界——————Integer
Integer integer2 = (Integer) list2.get(0); // // 此时返回取<? super Integer> 上界————Object

  所以同理可得,当泛型为<?>的时候,取下界是null,取上界是Object。

  所以得出结论,因为add和get方法的擦除的限制,尽量少使用通配泛型

  泛型擦除有什么隐患,有什么解决方法

  1、如果不加泛型继承,擦除后会变成原始类型,所以能加入非泛型的类型。 List<Integer> list = new ArrayList<Integer>(); list.add("呀哈");

    并且能完成不同泛型之间的引用传递。 List<String> list = new ArrayList<Integer>();

    以上两种情况怎么解决?      

    ——java编译器是通过先检查代码中泛型的类型,如果出现上面两种情况则会在编译期报错,检查通过后,然后再进行类型擦除,再进行编译。

    【注意:先检查实例化左右泛型是否匹配,然后以实例化的左边的泛型为基准对添加元素进行检查(所以,只在右边写泛型和没写是一个意思)】例如:

List<String> list1 = new ArrayList<String>(); // 此时按照String检查泛型
List list2 = new ArrayList<String>(); // 此时不检查泛型

  2、擦除后泛型信息就没了,获取的时候再强转?

    ——泛型补偿:在泛型检查的保证下,存入的都是符合泛型的对象,编译期间利用反射获取元素对象的类型(getClass()方法)对要传出元素进行强转。

  3、子类继承泛型方法,然后对其重写并将泛型改成真实类型,但是在擦除之后原来父类的泛型方法会变成Object方法,变为两个不同的方法,这样一来此方法就不是继承重写,而是子类的重载了。如下面代码所示:

class Node<T> {
public void setData(T data) {
System.out.println("Node.setData");
}
}
class MyNode<T> extends Node<Integer> {
public void setData(Integer data) {
System.out.println("MyNode.setData:"+data);
}
}
Node<Integer> n = new MyNode<Integer>();
n.setData(1213); // 如果是擦除后的Object方法则会执行父类的方法,打印出“Node.setData”

    运行结果: MyNode.setData:1213

   可见执行的却是子类的方法?完成了多态的实现。这是怎么解决的?

    ——桥方法:顾名思义,因为擦除之后子类中方法的参数列表与父类参数列表不同,不能形成重写,所以编译器在编译的时候,擦除后往子类中插入一些方法用来重载父类中的所有泛型擦除之后的Object方法,并在方法内部调用相对应的子类方法,以此重新形成父子之间多态,这些方法被称为桥方法。(下面是编译器擦除编译之后的内容)

class Node {
public void setData(Object data) {
System.out.println("Node.setData");
}
}
class MyNode extends Node {
// 编译器生成的桥方法
public void setData(Object data) {
setData((Integer) data);
}
public void setData(Integer data) {
System.out.println("MyNode.setData:"+data);
}
}

Java中泛型区别以及泛型擦除详解的更多相关文章

  1. java中List的用法和实例详解

    java中List的用法和实例详解 List的用法List包括List接口以及List接口的所有实现类.因为List接口实现了Collection接口,所以List接口拥有Collection接口提供 ...

  2. Java中堆内存和栈内存详解2

    Java中堆内存和栈内存详解   Java把内存分成两种,一种叫做栈内存,一种叫做堆内存 在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配.当在一段代码块中定义一个变量时,ja ...

  3. Java中的equals和hashCode方法详解

    Java中的equals和hashCode方法详解  转自 https://www.cnblogs.com/crazylqy/category/655181.html 参考:http://blog.c ...

  4. 转:Java中的equals和hashCode方法详解

    转自:Java中的equals和hashCode方法详解 Java中的equals方法和hashCode方法是Object中的,所以每个对象都是有这两个方法的,有时候我们需要实现特定需求,可能要重写这 ...

  5. java中的String类常量池详解

    test1: package StringTest; public class test1 { /** * @param args */ public static void main(String[ ...

  6. java中常见的六种线程池详解

    之前我们介绍了线程池的四种拒绝策略,了解了线程池参数的含义,那么今天我们来聊聊Java 中常见的几种线程池,以及在jdk7 加入的 ForkJoin 新型线程池 首先我们列出Java 中的六种线程池如 ...

  7. Java中堆内存和栈内存详解

    Java把内存分成两种,一种叫做栈内存,一种叫做堆内存 在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配.当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间 ...

  8. java集合(3)- Java中的equals和hashCode方法详解

    参考:http://blog.csdn.net/jiangwei0910410003/article/details/22739953 Java中的equals方法和hashCode方法是Object ...

  9. JAVA中GridBagLayout布局管理器应用详解

    很多情况下,我们已经不需要通过编写代码来实现一个应用程序的图形界面,而是通过强大的IDE工具通过拖拽辅以简单的事件处理代码即可很轻松的完成.但是我们不得不面对这样操作存在的一些问题,有时候我们希望能够 ...

  10. Java中的queue和deque对比详解

    队列(queue)简述 队列(queue)是一种常用的数据结构,可以将队列看做是一种特殊的线性表,该结构遵循的先进先出原则.Java中,LinkedList实现了Queue接口,因为LinkedLis ...

随机推荐

  1. 配置oem

    [oracle@kaifai ~]$ export ORACLE_SID=dbking[oracle@kaifai ~]$ export ORACLE_SID=kaifai[oracle@kaifai ...

  2. tow sum

    今天面试好打脸!!! 解决方案方法一:暴力法暴力法很简单.遍历每个元素 xx,并查找是否存在一个值与 target−x 相等的目标元素. public int[] twoSum(int[] nums, ...

  3. django-models的get与filter

    为了说明它们两者的区别定义2个models class Student(models.Model):name = models.CharField('姓名', max_length=20, defau ...

  4. CentOS 6.4下Squid代理服务器的安装与配置(转)

    add by zhj: 其实我们主要还是关注它在服务器端使用时,充当反向代理和静态数据缓存.至于普通代理和透明代理,其实相当于客户端做的事,和服务端没有什么关系.另外,Squid的缓存主要是缓存在硬盘 ...

  5. Python性能分析指南(未完成)

    英文原文:http://www.huyng.com/posts/python-performance-analysis/ 译文:http://www.oschina.net/translate/pyt ...

  6. java反射机制与动态代理

    在学习HadoopRPC时.用到了函数调用.函数调用都是採用的java的反射机制和动态代理来实现的,所以如今回想下java的反射和动态代理的相关知识. 一.反射 JAVA反射机制定义: JAVA反射机 ...

  7. 002-and design-dva.js 知识导图-01JavaScript 语言,React Component

    一.概述 参看:https://github.com/dvajs/dva-knowledgemap react 或 dva 时会不会有这样的疑惑: es6 特性那么多,我需要全部学会吗? react ...

  8. 详解PHP实现定时任务的五种方法

    这几天需要用PHP写一个定时抓取网页的服务器应用. 在网上搜了一下解决办法, 找到几种解决办法,现总结如下. 定时运行任务对于一个网站来说,是一个比较重要的任务,比如定时发布文档,定时清理垃圾信息等, ...

  9. /etc/rc.d/rc.local linux启动自动开启某些服务(转)

    /etc/rc.d/rc.local似乎是很多Linux系统管理员的偏爱,因为凡是需要随系统自动启动的服务.程序等,只要系统没有提供Sys V风格的启动脚本,就把这些需求都塞到/etc/rc.d/rc ...

  10. MySQL server has gone away With statement: INSERT INTO `students`......

    mysql出现ERROR : (2006, 'MySQL server has gone away') 的问题意思就是指client和MySQL server之间的链接断开了. 首选分析给出可能出现的 ...