泛型擦除
在编码阶段使用泛型时加上的类型参数,会被编译器在编译阶段去掉,这个过程叫做泛型擦除。
泛型主要用于编译阶段。在编译后生成的
Java
字节码文件中不包含泛型中的类型信息。例如,在编码时定义的List<Integer>
和List<String>
经过编译后统一为List。JVM
读取的只是List,由泛型附加的类型信息对JVM
来说是不可见的。
Java核心技术卷I解释:
无论何时定义一个泛型类型,都会自动提供一个相应的原始类型(raw type)。这个原始类型的名字就是去掉类型参数后的泛型类型名。类型变量会被擦除(erased),并替换为其限定类型(或者,对于无限定的变量则替换为Object
)。
例如,
泛型类Pair<T>
如下:
public class Pair<T>{
private T first;
private T second;
public Pair(T first,T second){
this.first=first;
this.second=second;
}
public T getFirst(){return first;}
public T getSecond(){return second;}
public void setFirst(T newValue){first=newValue;}
public void setSecond(T newValue){second=newValue;}
}
Pair<T>
的原始类型如下所示:
public class Pair{
private Object first;
private Object second;
public Pair(Object first,Object second){
this.first=first;
this.second=second;
}
public Object getFirst(){return first;}
public Object getSecond(){return second;}
public void setFirst(Object newValue){first=newValue;}
public void setSecond(Object newValue){second=newValue;}
}
因为T是一个无限定的变量,所以直接用Object替换。
在程序中可以包含不同类型的Pair,例如Pair<String>
或Pair<LocalDate>
。不过擦除类型后,它们都会编程原始的Pair类型。
假定我们声明了泛型上限:
代码语言:javascript复制public class Interval<T extends Comparable&Serializable> implements Serializable{
private T lower;
private T upper;
...
public Interval(T first,T second){
if(first.compareTo(second)<=0){lower=first;upper=second;}
else{lower=second;upper=first;}
}
}
此时原始类型如下所示:
代码语言:javascript复制public class Interval implements Serializable{
private Comparable lower;
private Comparable upper;
...
public Interval(Comparable first,Comparable second){
if(first.compareTo(second)<=0){lower=first;upper=second;}
else{lower=second;upper=first;}
}
}
如果将限定切换为
class Interval<T extends Serializable&Comparable>
,原始类型会用Serializable
替换T,而编译器在必要时要向Comparable插入强制类型转换。
(1)转换泛型表达式
编写一个泛型方法调用时,如果擦出了返回类型,编译器会插入强制类型转换。例如:
代码语言:javascript复制Pair<Employee> buddies=...;
Employee buddy=buddies.getFirst();
getFirst擦除类型后的返回类型是Object。编译器自动插入转换到Employee的强制类型转换。也就是说,编译器把这个方法调用转换为两条虚拟机指令:
- 对原始方法
Pair.getFirst
的调用。 - 将返回的Object类型强制转换为Employee类型。
当访问一个泛型字段时,也要插入强制类型转换。假设Pair类的first字段和second字段都是公共的。表达式
代码语言:javascript复制Employee buddy=buddies.first;
也会在结果字节码中插入强制类型转换。
(2)转换泛型方法
类型擦除也会出现在泛型方法中。
代码语言:javascript复制public static <T extends Comparable> T min(T[] a)
经过泛型擦除后,只剩下:
代码语言:javascript复制public static Comparable min(Comparable[] a);
注意,类型参数T已经被擦出了,只留下了限定类型Comparable。
泛型擦除带来的问题
当然,泛型擦除也带来了许多问题,这里就不细讲了,只是小总结下:
- 不能用基本类型实例化类型参数
- 运行时类型查询只适用于原始类型
- 不能创建参数化类型的数组
- 不能实例化类型变量
- 不能构造泛型数组
- 泛型类的静态上下文中类型变量无效
- 不能抛出或捕获泛型类的实例
- 可以取消对检查型异常的检查
- 注意擦除后的冲突
参考: 《剑指Java》 《Java核心技术卷I》 泛型详解 泛型之类型擦除