Java中泛型区别以及泛型擦除详解

1、引言java

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

 

2、泛型简介jvm

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

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

 

3、泛型分类对象

一、普通泛型  blog

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

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

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

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

 

二、通配泛型

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

  【无界通配符】

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

  【有界通配符】

  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接口

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

 

4、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>(); // 右边的"答案"与左边的“答案范围”匹配成功

 

5、泛型擦除

  由来:一开始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方法的擦除的限制,尽可能少使用通配泛型

 

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

  一、若是不加泛型继承,擦除后会变成原始类型,因此能加入非泛型的类型。 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>();  // 此时不检查泛型

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

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

  三、子类继承泛型方法,而后对其重写并将泛型改为真实类型,可是在擦除以后原来父类的泛型方法会变成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);  
    }  
}
相关文章
相关标签/搜索