天天看點

java泛型--橋方法

看橋方法之前,我們先來看看泛型中類型擦除的概念:

在《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檔案,看一下:

java泛型--橋方法

可以看到确實有兩個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.橋方法被用來保持多态;