1.
翻译泛型表达式:在程序调用泛型方法的时候,如果返回值被擦除,编译器会插入强制的类型转换。
如下两条语句
Pair birthdays =...;
GregorianCalendar first =birthdays.getFirst();
原始类型中方法getFirst()的返回被替换成Object,但是编译器会自动插入GregorianCalendar的强制类型转换。编译器会将这条语句翻译成两条虚拟机指令,并插入字节码:
对原始方法getFirst()的调用;
将返回的Object对象强制转换成GregorianCalendar。
当存取一个泛型域的时候也会在字节码中插入强制的类型转换。
public class ArrayAlg{
publicstatic T getMiddle(T[]t){
System.out.println("泛型方法");
return t[t.length/2];
}
publicstatic Pair minmax(T[]ts){
if(ts == null || ts.length == 0){
return null;
}
T min = ts[0];
T max = ts[0];
for(int i = 0;i length;i++){
if(min.compareTo(ts[i]) >0){
min = ts[i];
}
if(max.compareTo(ts[i]) <0){
max = ts[i];
}
}
return new Pair(min,max);
}
使用类分析器对其进行分析,结果:
publicstatic int getMiddle(int[]);
publicstatic Pair minmax(java.lang.Comparable[]);
泛型方法的类型擦除会带来两个问题1.类型擦除与多态的冲突;2.方法签名冲突。
我们来看一个结构相对繁杂一些的类,类DateInterval继承前面定义的泛型类Pair:
public class DateInterval extends Pair<Date> {
publicDateInterval(Date first, Date second){
super(first, second);
}
@Override
public void setSecond(Date second) {
super.setSecond(second);
}
@Override
public Date getSecond(){
return super.getSecond();
}
public static void main(String[] args) {
DateIntervalinterval = new DateInterval(new Date(), newDate());
Pair pair =interval;//超类,多态
Date date = new Date(2000, 1, 1);
System.out.println("原来的日期:"+pair.getSecond());
System.out.println("set进新日期:"+date);
pair.setSecond(date);
System.out.println("执行pair.setSecond(date)后的日期:"+pair.getSecond());
}
}
我们知道Java中的方法调用采用的是动态绑定的方式,应该呈现出多态的特性。子类覆写超类中的方法,如果将子类向下转型成超类后,仍然可以调用覆写后的方法。
但是泛型类的类型擦除造成了一个问题,Pair的原始类型中存在方法
public void setSecond(Object second);
DateInterval中的方法
public void setSecond(Date second);
我们的本意是想覆写Pair中的setSecond方法,但是从方法签名上看,这完全是两个不同的方法,类型擦除与多态产生了冲突。而实际情况那?运行DateInterval的main方法,我们看到
public void setSecond(Date second)的确覆写了public void setSecond(Object second)方法。这是如何做到的那?
使用Java类分析器对其进行分析,结果:
public class DateInterval extends Pair{
//构造器
public DateInterval(java.util.Date,java.util.Date);
//方法
public void setSecond(java.util.Date);
public volatile voidsetSecond(java.lang.Object);//方法1
public java.util.DategetSecond( );//方法2
public volatile java.lang.Object getSecond();//方法3,它难道不会和方法1冲突?
public static void main(java.lang.String[]);
}
方法1和方法3是我们在源码中不曾定义的,它肯定是由编译器生成的。这个方法称为桥方法(bridgemethod),真正覆写超类方法的是它。语句pair.setSecond(date)实际上调用的是方法1,public volatile void setSecond(Object),通过这个方法再去调用public void setSecond(Date)。这个桥方法的实际内容是:
public void setSecond(Objectsecond){
this.setSecond((java.util.Date) second );
}
这样的结果就符合面向对象中多态的特性了,实现了方法的动态绑定。但是,这样的做法给我们带来了一种错觉,就认为public voidsetSecond(Date)覆写了泛型类的public void setSecond(Object),如果我们在DateInterval中增加一个方法:
publicvoid setSecond(Object obj){
System.out.println("覆写超类方法!");
}
请再运行一次,观察这次的结果会有什么不同。有意思吧,我所使用的NetbeanIDE并没有提示我在这个方法前加上@Override,而它会提示你在setSecond(Date)方法前加上@Override的注释。现在我们知道了,这只是一个假象!
现在我们知道了方法3也是由编译器生成的桥方法,为了实现多态。方法擦除带来的第二个问题就是:由编译器生成的桥方法publicvolatile java.lang.Object getSecond()方法和public java.util.DategetSecond()方法,从方法签名的角度看是两个完全相同的方法,它们怎么可以共存那?如果是我们自己编写Java代码,这样的代码是无法通过编译器的检查的,但是虚拟机却是允许这样做的,因为虚拟机通过参数类型和返回类型来确定一个方法,所以编译器为了实现泛型的多态允许自己做这个看起来“不合法”的事情。
补充说明:从JDK1.5开始,在一个方法覆盖另一个方法时可以指定一个更严格的返回类型,它的机制也是同样使用的桥方法。例如:
public class A{
public ListgetList(){
return null;
}
}
public class ASub extendsA{
@Override
publicArrayList getList(){
return null;
}
}
分析ASub类,结果:
public class ASub extendsA{
//域
//构造器
public ASub();
//方法
publicjava.util.ArrayList getList( );
publicvolatile java.util.List getList( );
}
原文链接:
http://blog.sina.com.cn/s/blog_44c1e6da0100coxb.html