建议93:Java的泛型是类型擦除的

Java泛型(Generic)的引入加强了参数类型的安全性,减少了类型的转换,它与C++中的模板(Templates)比较类似,但是有一点不同的是:Java的泛型在编译期有效,在运行期被删除,也就是说所有的泛型参数类型在编译后都会被清除掉,我们来看一个例子,代码如下:


public class Foo{

//arrayMethod接收数组参数,并进行重载

public void arrayMethod(String[]strArray){

}

public void arrayMethod(Integer[]intArray){

}

//listMethod接收泛型List参数,并进行重载

public void listMethod(List<String>stringList){

}

public void listMethod(List<Integer>intList){

}

}


程序很简单,编写了4个方法,arrayMethod方法接收String数组和Integer数组,这是一个典型的重载,listMethod接收元素类型为String和Integer的List变量。现在的问题是,这段程序是否能编译?如果不能,问题出在什么地方?

事实上,这段程序是无法编译的,编译时报错信息如下:


Method listMethod(List<Integer>)has the same erasure listMethod(List<E>)as

another method in type Foo


此错误的意思是说listMethod(List<Integer>)方法在编译时擦除类型后的方法是listMethod(List<E>),它与另外一个方法重复,通俗地说就是方法签名重复。这就是Java泛型擦除引起的问题:在编译后所有的泛型类型都会做相应的转化。转换规则如下:

List<String>、List<Integer>、List<T>擦除后的类型为List。

List<String>[]擦除后的类型为List[]。

List<?extends E>、List<?super E>擦除后的类型为List<E>。

List<T extends Serializable&Cloneable>擦除后为List<Serializable>。

明白了这些擦除规则,再看如下代码:


public static void main(String[]args){

List<String>list=new ArrayList<String>();

list.add("abc");

String str=list.get(0);

}


经过编译器的擦除处理后,上面的代码与下面的程序是一致的:


public static void main(String[]args){

List list=new ArrayList();

list.add("abc");

String str=(String)list.get(0);

}


Java编译后的字节码中已经没有泛型的任何信息了,也就是说一个泛型类和一个普通类在经过编译后都指向了同一字节码,比如Foo<T>类,经过编译后将只有一份Foo.class类,不管是Foo<String>还是Foo<Integer>引用的都是同一字节码。Java之所以如此处理,有两个原因:

避免JVM的大换血。C++的泛型生命期延续到了运行期,而Java是在编译器擦除掉的,我们想想,如果JVM也把泛型类型延续到运行期,那么JVM就需要进行大量的重构工作了。

版本兼容。在编译期擦除可以更好地支持原生类型(Raw Type),在Java 1.5或1.6平台上,即使声明一个List这样的原生类型也是可以正常编译通过的,只是会产生警告信息而已。

明白了Java的泛型是类型擦除的,我们就可以解释类似如下的问题了:

(1)泛型的class对象是相同的

每个类都有一个class属性,泛型化不会改变class属性的返回值,例如:


public static void main(String[]args){

List<String>ls=new ArrayList<String>();

List<Integer>li=new ArrayList<Integer>();

System.out.println(li.getClass()==li.getClass());

}


以上代码将返回为true,原因很简单,List<String>和List<Integer>擦除后的类型都是List,没有任何区别。

(2)泛型数组初始化时不能声明泛型类型

如下代码编译时通不过:


List<String>[]listArray=new List<String>[];


原因很简单,可以声明一个带有泛型参数的数组,但是不能初始化该数组,因为执行了类型擦除操作,List<Object>[]与List<String>[]就是同一回事了,编译器拒绝如此声明。

(3)instanceof不允许存在泛型参数

以下代码不能通过编译,原因一样,泛型类型被擦除了:


List<String>list=new ArrayList<String>();

System.out.println(list instanceof List<String>);