Java泛型小结(三)
Java泛型总结(三)
泛型代码和虚拟机
1)类型变量的擦除
虚拟机并没有泛型类型的对象,所有的对象都是普通类,即使是我们看到的泛型类型(如Pair<String>),在编译后都成了普通的类型。任何时候,泛型类型都自动对应一个相应的原型(raw type),就是删除类型变量后的泛型类型名。
如何擦除类型变量呢。对于有限定类型的,如T extends Comparable 的删除T,并替换成限定的类型Comparable ;没有限定类型的则替换成Object。
注意到,如果有多个限定类型的呢,例如T extends Serializable & Comparable
这个时候,将把T替换成Serializable,因为编译器用的是限定类型中的第一个。但是,替换成Serializable,编译器在编译的时候当需要接口Comparable的compareTo方法时又需要进行强制转换,为了不影响效率,应该替换成Comparable比较好。那么,我们应该把Comparable放在前面,即:T extends Comparable & Serializable。
因此,我们总结出结论:为了提高效率,应该把tagging interfaces(标签接口,就是没有方法的接口)放在限定类型列表的最后。
2)泛型的翻译
当程序调用泛型方法时,如果擦除返回类型,编译器插入强制类型转换。
When you program a call to a generic method, the compiler inserts casts when the return type has been erased
接着第一篇中的例子:
Pair<Employee> pair = new Pair<Employee>();
Employee first = pair.getFirst();
本来getFirst方法返回的类型是T,按照上面所述的类型擦除后返回类型变为Object,编译器又自动插入Employee的强制类型转换,所以直接就得到了Employee类型了。
3)桥方法(bridge method)
桥方法用来保持多态,解决类型擦除与多态的冲突。
例如,存在泛型类Pair<T>
public class Pair<T> {
private T second ;
public void setSecond(T second) {
this. second = second;
}
}
自定义类MyDate继承了Pair<Date>
class MyDate extends Pair<Date> {
@Override
public void setSecond(Date second) {
…… ;
}
}
在进行类型擦除后变成了:
class MyDate extends Pair { // 去掉了泛型变量Date
@Override
public void setSecond(Date second) {
…… ;
}
}
当调用setSecond方法时:
MyDate myDate = new MyDate();
Pair<Date> pair = myDate;
pair.setSecond( new Date());
这里的pair声明是Pair<Date>类型,并且仅有一个方法setSecond(Object second),但它实际引用的是MyDate对象,那么语句pair.setSecond( new Date());在调用时,实际调用的是MyDate中的setSecond(Object second)方法,但是我们看到MyDate中并没有定义这个一个方法,而只有setSecond(Date second)方法。这时,动态和擦除的冲突就出现了。那么,桥方法就出现了,编译器在编译的时候,为MyDate生成这样的一个方法:
public void setSecond(Object second){ setSecond(Date) second; }
也就是,对其中的入参second做了强制转换成Date类型了。