一、引言

  复习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. 微软官方:SELECT语句逻辑处理顺序

    以下步骤显示SELECT 语句的逻辑处理顺序或绑定顺序.此顺序确定在一个步骤中定义的对象何时可用于后续步骤中的子句. 例如,如果查询处理器可以绑定到(访问)在FROM 子句中定义的表或视图,则这些对象 ...

  2. CentOS7 部署tomcat

    一.环境说明: 在 CentOS7下面部署tomcat7 . 二.tomcat部署 1.新建tomcat目录  mkdir tomcat 2.上传tomcat 安装文件   apache-tomcat ...

  3. zabbix 部署 jmx 监控tomcat

    zabbix提供了一个java gateway的应用去监控jmx(Java Management Extensions,即Java管理扩展)是一个为应用程序.设备.系统等植入管理功能的框架.JMX可以 ...

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

    一.简介 代理服务器英文全称是Proxy Server,其功能就是代理网络用户去取得网络信息. Squid是一个缓存Internet 数据的软件,其接收用户的下载申请,并自动处理所下载的数据.当一个用 ...

  5. 自定义Realm解析

    自定义Realm解析---------------------------------------> /* * Copyright 2005-2013 shopxx.net. All right ...

  6. poj3764 The XOR Longest Path【dfs】【Trie树】

    The xor-longest Path Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 10038   Accepted:  ...

  7. MYSQL查看数据表最后更新时间

    MYSQL查看数据表最后更新时间 - 拨云见日 - CSDN博客 https://blog.csdn.net/warnerwu/article/details/73352774 mysql> S ...

  8. CVP沙龙

    关于职场: 35岁之后,还去招聘网站投简历? 35岁可能是个分水岭 95后比一些80后还强, 有些80后玻璃心 35岁有的可能已经是VP了 应该深入积累而不是蜻蜓点水 只有第一年成长了,之后是重复劳动 ...

  9. apt-get install 和 pip install的区别

    pip install apt-get install 源是pyPI 源是ubuntu仓库 对于同一个包,pyPI可以提供更多的版本以供下载 pip install安装的python包,可以只安装在当 ...

  10. HDU Today---hdu2112(最短路-_-坑在是无向图)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2112 spfa或者迪杰斯特拉都可以 注意公交车是有来回的--- #include <iostre ...