看橋方法之前,我們先來看看泛型中類型擦除的概念:
在《java核心卷書卷1》中有這樣一段描述:
虛拟機沒有泛型類型對象——所有對象都屬于普通類。也就是說,虛拟機在執行代碼的時候,都會把泛型類翻譯成普通的類。
無論何時定義一個泛型類型,都自動提供了一個相應的原始類型(raw type)。原始類型的名字就是删去類型參數後的泛型類型名。擦除(erased)類型變量,并替換為限定類型(無限定的變量用Object)。
下面看一個泛型類:
class Pair<T> {
private T first;
private T second;
public Pair(T first, T second) {
super();
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public T getSecond() {
return second;
}
}
它的原始類型是(T沒有限定,用Object替換):
class Pair {
private Object first;
private Object second;
public Pair2(Object first, Object second) {
super();
this.first = first;
this.second = second;
}
public Object getFirst() {
return first;
}
public Object getSecond() {
return second;
}
}
當然了,一個類型變量或通配符可以有多個限定,例如:T extends Comparable & Serializble。這樣的該怎樣進行替換呢?原始類型用第一個限定的類型變量來限定。
Eg:
class Pair<T extends Comparable & Serializable> {
private T first;
private T second;
public Pair(T first, T second) {
super();
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public T getSecond() {
return second;
}
}
替換完後就變成了(用第一個限定的類型變量Comparable作為原始類型):
class Pair implements Serializable>{
private Comparable first;
private Comparable second;
public Pair(Comparable first, Comparable second) {
super();
this.first = first;
this.second = second;
}
public Comparable getFirst() {
return first;
}
public Comparable getSecond() {
return second;
}
}
了解了類型擦除的概念後,看看橋方法的産生:
先看一個例子:
class Father<T> {
public void fun(T x) {
}
}
class Son extends Father<String> {
@Override
public void fun(String x) {
}
}
Father類和Son類經過類型擦除後變成:
class Father<Object> {
public void fun(Object x) {
}
}
class Son extends Father {
@Override
public void fun(String x) {
}
}
很顯然,子類Son是想覆寫父類Father的fun方法,但是卻出現了問題(注意重寫的要求:方法名、參數個數和參數類型都必須相同)。因為Father類在編譯階段經過類型擦除後,T被替換成了Object了。也就是Father類中的fun方法變成了:
public void fun(Object x) {
}
這對于多态是個不小的麻煩,考慮下面的代碼:
Father<String> f=new Son();
Object x=null;
f.fun(x);
本來是想利用多态調用Son類的fun(Object)方法,但是Son類的卻是fun(String),沒有fun(Object)方法。這就導緻類型擦除和多态産生了沖突。
下面來看看編譯器是怎麼解決這個問題的:
編譯器在Son類中生成了一個橋方法(bridgemethod),我們可以通過javap反編譯Son.class檔案,看一下:
可以看到确實有兩個fun方法:fun(Object)、fun(String)
在生成的橋方法中是怎麼操作的呢:
public void fun(Object x){
fun((String)x);
}
現在再來看一下f.fun(Object),由于f引用的Son類對象,是以根據多态性質,會調用Son類的fun(Object)方法,這個方法是合成的橋方法。它會去調用Son類的fun(String)方法,這正是我們所期望的結果。
橋方法解決了多态和類型擦除之間的沖突,但是假如Father類中還有一個方法:
public T gun(){
///
}
在Son中也想覆寫這個方法呢?
就會發現在Son類中出現了兩個方法簽名一樣的方法(方法名和參數都相同):
public Object gun()
public String gun()
當然,我們是不能這樣編寫代碼的,因為不能有兩個名字相同,參數類型也相同卻傳回不同類型值的方法。
但是,在虛拟機中卻是可以的(很神奇),虛拟機用參數類型和傳回類型确定一個方法(《java核心卷術卷1》)。編譯器可能産生兩個僅傳回值不同的方法位元組碼,虛拟機能夠正确的處理這一情況。
橋方法不僅用于泛型類,可以看一個例子:
class Person implements Cloneable {
@Override
public Person clone() throws CloneNotSupportedException {
return (Person) super.clone();
}
}
在一個方法覆寫另一個方法時,可以指定一個更加嚴格的傳回類型。
實際上Person類也有兩個clone方法:
Employee clone()
Object clone()//合成的橋方法,會調用新定義的方法。
總之,需要記住有關Java泛型轉換的事實:
1.虛拟機中沒有泛型,隻有普通的類和方法;
2.所有的類型參數都用他們的限定類型替換;
3.橋方法被用來保持多态;