8.5泛型代码和虚拟机
虚拟机中并不存在泛型类型的对象——虚拟机中所有的对象都是普通类类型的。下面讲解编译器是如何“擦除”类型参数的,类型擦除又对我们程序员有什么影响。
8.5.1 类型擦除(编译器对泛型类的处理)
当你定义一个泛型类型的时候,一个对应的原始类型(raw type)会自动生成。这个原始类型跟对应的泛型类型①名称相同,②删除了类型参数,③类型变量被替换成它的边界列表中的第一个边界(无边界的类型变量被替换为Object).
例如,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;}
}
被编译器处理后的泛型类最后就成了一个普通类,在没有泛型的时代里我们就是这么实现通用的Pair的。
程序中的Pair<String>,Pair<LocalDate>等各种泛型Pair,最终都被擦除成为原始类型Pair。
C++Note:
C++中一个模板的每种实例都会对应产生一种类型,无疑增加了最终程序的代码行,Java就没有这个毛病
同理
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;}
}
}
擦除后变为原始类型
public class Interval implements Serializable{
private Comparable lower;
private Comparable upper;
...
public Interval(Comparable first, Comparable second){...}
}
Note
如果一个类型参数有多个边界,则类型变量会被替换成边界列表中的第一个边界,在必要时,编译器会插入cast将类型变量cast为其他边界类型,在上面的例子中,如果把边界Serializable放在前面,那么在compareTo方法中,方法的形参就会被cast为Comparable类型。所以:标签接口(标签接口是没有方法的接口)边界应该尽可能放在列表后面,方法被使用较多的边界则放在前面,这样可以减少cast的次数,提高效率。