【java】--泛型-型別擦除與多型的衝突和解決方法
現在有這樣一個泛型類:
[java] view plain copy print ?- class Pair<T> {
- private T value;
- public T getValue() {
- return value;
- }
- public void setValue(T value) {
- this.value = value;
- }
- }
class Pair<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
然後我們想要一個子類繼承它
[java] view plain copy print
- class DateInter extends Pair<Date> {
- @Override
- public void setValue(Date value) {
- super.setValue(value);
- }
- @Override
- public Date getValue() {
- return super.getValue();
- }
- }
class DateInter extends Pair<Date> {
@Override
public void setValue(Date value) {
super.setValue(value);
}
@Override
public Date getValue() {
return super.getValue();
}
}
在這個子類中,我們設定父類的泛型型別為Pair<Date>,在子類中,我們覆蓋了父類的兩個方法,我們的原意是這樣的:
將父類的泛型型別限定為Date,那麼父類裡面的兩個方法的引數都為Date型別:“
[java] view plain copy print ?- public Date getValue() {
- return value;
- }
- public void setValue(Date value) {
- this.value = value;
- }
public Date getValue() {
return value;
}
public void setValue(Date value) {
this.value = value;
}
所以,我們在子類中重寫這兩個方法一點問題也沒有,實際上,從他們的@Override標籤中也可以看到,一點問題也沒有,實際上是這樣的嗎?
分析:
實際上,型別擦除後,父類的的泛型型別全部變為了原始型別Object,所以父類編譯之後會變成下面的樣子:
[java] view plain copy print ?- class Pair {
- private Object value;
- public Object getValue() {
- return value;
- }
- public void setValue(Object value) {
- this.value = value;
- }
- }
class Pair {
private Object value;
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
再看子類的兩個重寫的方法的型別:
[java]
view plain
copy
print
?
- @Override
- public void setValue(Date value) {
- super.setValue(value);
- }
- @Override
- public Date getValue() {
- return super.getValue();
- }
@Override
public void setValue(Date value) {
super.setValue(value);
}
@Override
public Date getValue() {
return super.getValue();
}
先來分析setValue方法,父類的型別是Object,而子類的型別是Date,引數型別不一樣,這如果實在普通的繼承關係中,根本就不會是重寫,而是過載。
我們在一個main方法測試一下:
[java] view plain copy print ?
- public static void main(String[] args) throws ClassNotFoundException {
- DateInter dateInter=new DateInter();
- dateInter.setValue(new Date());
- dateInter.setValue(new Object());//編譯錯誤
- }
public static void main(String[] args) throws ClassNotFoundException {
DateInter dateInter=new DateInter();
dateInter.setValue(new Date());
dateInter.setValue(new Object());//編譯錯誤
}
如果是過載,那麼子類中兩個setValue方法,一個是引數Object型別,一個是Date型別,可是我們發現,根本就沒有這樣的一個子類繼承自父類的Object型別引數的方法。所以說,卻是是重寫了,而不是過載了。
為什麼會這樣呢?
原因是這樣的,我們傳入父類的泛型型別是Date,Pair<Date>,我們的本意是將泛型類變為如下:
[java] view plain copy print ?- class Pair {
- private Date value;
- public Date getValue() {
- return value;
- }
- public void setValue(Date value) {
- this.value = value;
- }
- }
class Pair {
private Date value;
public Date getValue() {
return value;
}
public void setValue(Date value) {
this.value = value;
}
}
然後再子類中重寫引數型別為Date的那兩個方法,實現繼承中的多型。
可是由於種種原因,虛擬機器並不能將泛型型別變為Date,只能將型別擦除掉,變為原始型別Object。這樣,我們的本意是進行重寫,實現多型。可是型別擦除後,只能變為了過載。這樣,型別擦除就和多型有了衝突。JVM知道你的本意嗎?知道!!!可是它能直接實現嗎,不能!!!如果真的不能的話,那我們怎麼去重寫我們想要的Date型別引數的方法啊。
於是JVM採用了一個特殊的方法,來完成這項功能,那就是橋方法。
首先,我們用javap -c className的方式反編譯下DateInter子類的位元組碼,結果如下:
- class com.tao.test.DateInter extends com.tao.test.Pair<java.util.Date> {
- com.tao.test.DateInter();
- Code:
- 0: aload_0
- 1: invokespecial #8 // Method com/tao/test/Pair."<init>"
- :()V
- 4: return
- public void setValue(java.util.Date); //我們重寫的setValue方法
- Code:
- 0: aload_0
- 1: aload_1
- 2: invokespecial #16 // Method com/tao/test/Pair.setValue
- :(Ljava/lang/Object;)V
- 5: return
- public java.util.Date getValue(); //我們重寫的getValue方法
- Code:
- 0: aload_0
- 1: invokespecial #23 // Method com/tao/test/Pair.getValue
- :()Ljava/lang/Object;
- 4: checkcast #26 // class java/util/Date
- 7: areturn
- public java.lang.Object getValue(); //編譯時由編譯器生成的巧方法
- Code:
- 0: aload_0
- 1: invokevirtual #28 // Method getValue:()Ljava/util/Date 去呼叫我們重寫的getValue方法
- ;
- 4: areturn
- public void setValue(java.lang.Object); //編譯時由編譯器生成的巧方法
- Code:
- 0: aload_0
- 1: aload_1
- 2: checkcast #26 // class java/util/Date
- 5: invokevirtual #30 // Method setValue:(Ljava/util/Date; 去呼叫我們重寫的setValue方法
- )V
- 8: return
- }
class com.tao.test.DateInter extends com.tao.test.Pair<java.util.Date> {
com.tao.test.DateInter();
Code:
0: aload_0
1: invokespecial #8 // Method com/tao/test/Pair."<init>"
:()V
4: return
public void setValue(java.util.Date); //我們重寫的setValue方法
Code:
0: aload_0
1: aload_1
2: invokespecial #16 // Method com/tao/test/Pair.setValue
:(Ljava/lang/Object;)V
5: return
public java.util.Date getValue(); //我們重寫的getValue方法
Code:
0: aload_0
1: invokespecial #23 // Method com/tao/test/Pair.getValue
:()Ljava/lang/Object;
4: checkcast #26 // class java/util/Date
7: areturn
public java.lang.Object getValue(); //編譯時由編譯器生成的巧方法
Code:
0: aload_0
1: invokevirtual #28 // Method getValue:()Ljava/util/Date 去呼叫我們重寫的getValue方法
;
4: areturn
public void setValue(java.lang.Object); //編譯時由編譯器生成的巧方法
Code:
0: aload_0
1: aload_1
2: checkcast #26 // class java/util/Date
5: invokevirtual #30 // Method setValue:(Ljava/util/Date; 去呼叫我們重寫的setValue方法
)V
8: return
}
從編譯的結果來看,我們本意重寫setValue和getValue方法的子類,竟然有4個方法,其實不用驚奇,最後的兩個方法,就是編譯器自己生成的橋方法。可以看到橋方法的引數型別都是Object,也就是說,子類中真正覆蓋父類兩個方法的就是這兩個我們看不到的橋方法。而打在我們自己定義的setvalue和getValue方法上面的@Oveerride只不過是假象。而橋方法的內部實現,就只是去呼叫我們自己重寫的那兩個方法。
所以,虛擬機器巧妙的使用了巧方法,來解決了型別擦除和多型的衝突。
不過,要提到一點,這裡面的setValue和getValue這兩個橋方法的意義又有不同。
setValue方法是為了解決型別擦除與多型之間的衝突。
而getValue卻有普遍的意義,怎麼說呢,如果這是一個普通的繼承關係:
那麼父類的setValue方法如下:
[java] view plain copy print ?- public ObjectgetValue() {
- return super.getValue();
- }
public ObjectgetValue() {
return super.getValue();
}
而子類重寫的方法是:
[java]
view plain
copy
print
?
- public Date getValue() {
- return super.getValue();
- }
public Date getValue() {
return super.getValue();
}
其實這在普通的類繼承中也是普遍存在的重寫,這就是協變。
關於協變:。。。。。。
並且,還有一點也許會有疑問,子類中的巧方法 Object getValue()和Date getValue()是同 時存在的,可是如果是常規的兩個方法,他們的方法簽名是一樣的,也就是說虛擬機器根本不能分別這兩個方法。如果是我們自己編寫Java程式碼,這樣的程式碼是無法通過編譯器的檢查的,但是虛擬機器卻是允許這樣做的,因為虛擬機器通過引數型別和返回型別來確定一個方法,所以編譯器為了實現泛型的多型允許自己做這個看起來“不合法”的事情,然後交給虛擬器去區別。