Java泛型的类型擦除

来源:互联网 发布:弹琴吧吉他软件 编辑:程序博客网 时间:2024/05/17 22:21
一、泛型简介

    所谓的泛型,即将类型参数化。主要思想是将算法和数据结构完全分离开,使得一次定义的算法能够提供多种数据结构使用,从而实现高度可重用的开发。在Java中,可以定义泛型类,泛型接口以及泛型方法。

     C++和Java都提供了对泛型的支持,但它们各自处理泛型的方式却截然不同。C++的编译器使用Code Specialization的方式——在实例化一个泛型类或泛型方法时都产生一份新的目标代码(字节码or二进制代码)。例如,针对一个泛型list<>,可能需要针对String,Integer分别产生二份不同的目标代码;而Java的编译器在处理泛型时采用的是Code Sharing的方式——对每个泛型类只生成唯一的一份目标代码;该泛型类的所有实例都映射到这份目标代码上,编译器会在必要的时候执行类型检查和类型转换的工作。

二、类型擦除

    JAVA编译器将多种泛型类形实例映射到唯一的字节码表示是通过类型擦除来实现的。类型擦除的关键在于从泛型类型中清除类型参数的相关信息,并且再必要的时候添加类型检查和类型转换的方法。擦除遵循这样的原则: 1.所有的有限定边界的泛型参数用其对应的最左边界类型来替换;2.无限定的泛型参数使用Object类型来替换。类型T的擦除记为|T|;参数化类型G<T1, T2, T3, ...>的擦除记为|G|;嵌套类型T.C的擦除记为|T|.C;数组类型T[ ] 的擦除记为|T|[ ]。下面举几个简单的例子加以说明:

[java] view plaincopy
  1. public class GenericMemoryCell<AnyTpye> {  
  2.   
  3.   private AnyTpye storedValue;  
  4.   public AnyTpye read(){  
  5.     return storedValue;  
  6.   }  
  7.   public void write(AnyTpye x){  
  8.     storedValue = x;  
  9.   }  
  10. }      
    GenericMemoryCell类是个泛型类,AnyType为其类型,由于AnyType没有加以任何限定的边界,故在擦除时使用Object类型类替换,擦除以后结果如下:

[java] view plaincopy
  1. public class GenericMemoryCell {  
  2.   
  3.   private Object storedValue;  
  4.   public Object read(){  
  5.     return storedValue;  
  6.   }  
  7.   public void write(Object x){  
  8.     storedValue = x;  
  9.   }  
  10. }     
    可以看到,经过擦除以后,代码内部没有了任何有关泛型参数的信息,它已经完全丢失。使用下面的代码来测试上面的泛型类:

[java] view plaincopy
  1. GenericMemoryCell<Integer> xx = new GenericMemoryCell<Integer>();  
  2. xx.write(3);  
  3. int x=xx.read();  
    使用ByteCode Outline查看对应的字节码,write方法对应的字节码如下:
        ICONST_3
        INVOKESTATIC Integer.valueOf(int) : Integer
        INVOKEVIRTUAL GenericMemoryCell.write(Object) : void

    由write方法的参数为Object类型可知,该方法实际上将Integer类型的对象作为Object类型的对象写入。而对应的read方法调用字节码如下:

       INVOKEVIRTUAL GenericMemoryCell.read() : Object
      CHECKCAST Integer
      INVOKEVIRTUAL Integer.intValue() : int

    可以看到read方法调用实际返回的是个Object类型的对象,编译器使用“CHECKCAST Integer”来检测是否能够将其转化为Integer类型,如何可以则执行转换。否则将抛出ClassCastException异常。由上可知,由于擦除的存在,编译器在必要的时候必须执行类型检查和转换的工作。下面是类型擦除的另一个例子:

[java] view plaincopy
  1. public static <T extends BoundingType> T method( T[] a){}  
    由于类型擦除的存在,且泛型参数T有限定边界BoudingType,因此擦除之后为:
[java] view plaincopy
  1. public static BoudingType method(BoudingType[] a){}  
三、使用泛型的限制
    由于擦除的存在,因此在使用java的泛型时应该特别小心。在泛型代码内部,我们无法获取有关泛型参数的任何信息,因此任何运行时需要知道确切类型信息的操作都无法完成,以下是使用时候的一些限制:

    1.不能实例化类型变量,不能创建泛型数组对象。也就是说不能使用new T() , new T[ ] , 或者T.class这样的表达式。因为T可能由Object或者其边界代替,因此这样的调用是没有意义的。

    2.在一个泛型类中,static方法或者static域均不可引用类的类型变量。

    3.不能实例化参数化类型的数组,即GenericMemoryCell <String> [ ] arr = new GenericMemoryCell <String> [10 ] ; 该操作是非法的。

    4.不能使用基本数据类型来实例化类型参数,应该使用其对应的包装类;即 GenericMemoryCell <int> 是不合法的。

    5.不能使用instanceof表达式,instanceof检测和类型转换工作只对原始类型有效。

    诸如以上的限制等,都是由于擦除引起的,应该特别小心对待。


转载自:http://blog.csdn.net/hust_is_lcd/article/details/7875386

0 0