所谓的泛型,即将类型参数化。主要思想是将算法和数据结构完全分离开,使得一次定义的算法能够提供多种数据结构使用,从而实现高度可重用的开发。在Java中,可以定义泛型类,泛型接口以及泛型方法。
C++和Java都提供了对泛型的支持,但它们各自处理泛型的方式却截然不同。C++的编译器使用Code Specialization的方式——在实例化一个泛型类或泛型方法时都产生一份新的目标代码(字节码or二进制代码)。例如,针对一个泛型list<>,可能需要针对String,Integer分别产生二份不同的目标代码;而Java的编译器在处理泛型时采用的是Code Sharing的方式——对每个泛型类只生成唯一的一份目标代码;该泛型类的所有实例都映射到这份目标代码上,编译器会在必要的时候执行类型检查和类型转换的工作。
JAVA编译器将多种泛型类形实例映射到唯一的字节码表示是通过类型擦除来实现的。类型擦除的关键在于从泛型类型中清除类型参数的相关信息,并且再必要的时候添加类型检查和类型转换的方法。擦除遵循这样的原则: 1.所有的有限定边界的泛型参数用其对应的最左边界类型来替换;2.无限定的泛型参数使用Object类型来替换。类型T的擦除记为|T|;参数化类型G
public class GenericMemoryCell {
private AnyTpye storedValue;
public AnyTpye read(){
return storedValue;
}
public void write(AnyTpye x){
storedValue = x;
}
}
GenericMemoryCell类是个泛型类,AnyType为其类型,由于AnyType没有加以任何限定的边界,故在擦除时使用Object类型类替换,擦除以后结果如下:
public class GenericMemoryCell {
private Object storedValue;
public Object read(){
return storedValue;
}
public void write(Object x){
storedValue = x;
}
}
可以看到,经过擦除以后,代码内部没有了任何有关泛型参数的信息,它已经完全丢失。使用下面的代码来测试上面的泛型类:
GenericMemoryCell xx = new GenericMemoryCell();
xx.write(3);
int x=xx.read();
使用ByteCode Outline查看对应的字节码,write方法对应的字节码如下:
INVOKEVIRTUAL GenericMemoryCell.read() : Object
CHECKCAST Integer
INVOKEVIRTUAL Integer.intValue() : int
可以看到read方法调用实际返回的是个Object类型的对象,编译器使用“CHECKCAST Integer”来检测是否能够将其转化为Integer类型,如何可以则执行转换。否则将抛出ClassCastException异常。由上可知,由于擦除的存在,编译器在必要的时候必须执行类型检查和转换的工作。下面是类型擦除的另一个例子:
public static T method( T[] a){}
由于类型擦除的存在,且泛型参数T有限定边界BoudingType,因此擦除之后为:
public static BoudingType method(BoudingType[] a){}
1.不能实例化类型变量,不能创建泛型数组对象。也就是说不能使用new T() , new T[ ] , 或者T.class这样的表达式。因为T可能由Object或者其边界代替,因此这样的调用是没有意义的。
2.在一个泛型类中,static方法或者static域均不可引用类的类型变量。
3.不能实例化参数化类型的数组,即GenericMemoryCell
4.不能使用基本数据类型来实例化类型参数,应该使用其对应的包装类;即 GenericMemoryCell
5.不能使用instanceof表达式,instanceof检测和类型转换工作只对原始类型有效。
诸如以上的限制等,都是由于擦除引起的,应该特别小心对待。