8.5.3 泛型方法的编译
泛型方法跟泛型类一样,也会被擦除。如下面的泛型方法:
public static <T extends Comparable> T min(T[] a)
擦除之后会变成:
public static Comparable min(Comparable[] a)
考虑下面的例子:
class Pair<T>{
public void setSecond(T second){...}
...
}
class DateInterval extends Pair<LocalDate>{
public void setSecond(LocalDate second){
if(second.compareTo(getFirst()) >= 0)
super.setSecond(second);
}
...
}
DateInterval继承了一个泛型类Pair<LocalDate>,我们试图重写setSecond方法,并在重写的方法中将类型变量T替换为了具体的类型LocalDate。(这是很自然的,因为子类确实只能存储LocalDate类型的Pair)
接着该类便被擦除为:
class DateInterval extends Pair{
public void setSecond(LocalDate second){...}
...
}
我们真的重写了父类的方法吗:看一下子类IntervalDate继承父类Pair的setSecond方法到底是个啥:
public void setSecond(Object second)
我去,这个参数是Object,这明显是没有实现重写,参数类型都不一样。
考虑下面的语句:
DateInterval interval = new DateInterval(...);
Pair<LocalDate> pair = interval;
pair.setSecond(aDate);
我们来理理下面的语句的执行流程:
pair.setSecond(aDate);
变量pair的声明类型是Pair<LocalDate>,这个类只有一个setSecond方法,即setSecond(Object second)。因为成员方法是动态绑定的,VM对pair引用的真正对象调用这个setSecond(Object second)方法。而这个pair变量所指向的真实对象是DateInterval类型。所以,方法DateInterval.setSecond(Object second)被调用(动态绑定)。
但是我们在前面看到,方法DateInterval.setSecond(Object second)仅仅是从Pair继承来的,并没有被重写,这么看来执行的还是父类的DateInterval.setSecond(Object second)版本。这就与我们的意图不相符了,我们本意是在子类中用带具体类型的方法重写父类的泛型方法,在调用方法时能具有多态现象(毕竟我们以前都是这么干的),但残酷的现实是:我们重写的方法没有覆盖父类的方法,我们试图实现的多态看起来也不能实现。
为了满足我们的意愿,编译器大发慈悲,替我们在子类中编写了我们想要的方法——就是我们没有实现的覆盖父类方法的方法:
public void setSecond(Object second){ setSecond( (LocalDate)second ); }
注意方法体里的cast,将泛型的边界类型cast成为我们提供的具体类型。
这个自己蹦出来的方法覆盖了父类的setSecond(Object),并调用了子类的setSecond(LocalDate)。这样通过这个方法,我们在调用上面的代码
pair.setSecond(aDate);
时,就在方法DateInterval.setSecond(Object)和DateInterval.setSecond(LocalDate)之间架起了一座桥梁,最终实际调用的就是我们自己试图重写父类的那个方法,最终实现了多态,符合了我们的直观,也满足了我们的意愿。这个方法也被赋予一个形象的名字——bridge方法
假设DateInterval也重写了getSecond:
class DateInterval extends Pair<LocalDate>{
public LocalDate getSecond(){return (LocalDate)super.getSecond();}
...
}
虽然编译器仅靠方法签名(名字和参数)确认一个方法的独一无二,但是VM却同时考虑方法签名和返回值类型。在VM那里,即便两个方法签名相同,但如果返回值不同,仍然可以被认为是两个独立的方法而不发生冲突。
这样一来,看起来我们子类中的getSecond遇到了跟setSecond同样的问题:没有重写父类方法。因为在VM看来,子类中实际有两个getSecond:
LocalDate getSecond() //① 我们希望重写父类方法所提供的子类方法
Object getSecond() // ② 从父类继承的方法
这时候,bridge方法又发挥作用了,编译器帮我们生成一个bridge方法:它重写了从父类继承来的方法②,并在方法内部调用我们自己编写的有具体返回值的方法①。编译器一顿操作之后,实际效果再一次都跟我们的直观相符了。
Note
bridge方法的舞台不止局限于泛型。它还出现在另一个地方:协变返回值类型。考虑下面的例子:
`public class Employee implements Clonable{public Employee clone() throws CloneNotSupportedException{…}
}
我们用协变返回值类型重写了父类Object的clone方法,其实子类Employee中有两个clone方法:
Employee clone() // 这是我们写的(我们想重写但没成功)
Object clone() // 这是继承Object父类的,被编译器提供的bridge重写,用来
桥接我们写的方法。
最后的结果就是我们的协变返回值类型的方法在效果上重写了父类方法。