1. 程式人生 > >【java】--泛型-型別擦除與多型的衝突和解決方法

【java】--泛型-型別擦除與多型的衝突和解決方法

型別擦除與多型的衝突和解決方法

現在有這樣一個泛型類:

[java] view plain copy print ?
  1. class Pair<T> {  
  2.     private T value;  
  3.     public T getValue() {  
  4.         return value;  
  5.     }  
  6.     public void setValue(T value) {  
  7.         this.value = value;  
  8.     }  
  9. }  
class Pair<T> {
	private T value;
	public T getValue() {
		return value;
	}
	public void setValue(T value) {
		this.value = value;
	}
}

然後我們想要一個子類繼承它
[java] view plain copy print
?
  1. class DateInter extends Pair<Date> {  
  2.     @Override  
  3.     public void setValue(Date value) {  
  4.         super.setValue(value);  
  5.     }  
  6.     @Override  
  7.     public Date getValue() {  
  8.         return super.getValue();  
  9.     }  
  10. }  
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 ?
  1. public Date getValue() {  
  2.     return value;  
  3. }  
  4. public void setValue(Date value) {  
  5.     this.value = value;  
  6. }  
	public Date getValue() {
		return value;
	}
	public void setValue(Date value) {
		this.value = value;
	}
 
所以,我們在子類中重寫這兩個方法一點問題也沒有,實際上,從他們的@Override標籤中也可以看到,一點問題也沒有,實際上是這樣的嗎?


分析:

實際上,型別擦除後,父類的的泛型型別全部變為了原始型別Object,所以父類編譯之後會變成下面的樣子:

[java] view plain copy print ?
  1. class Pair {  
  2.     private Object value;  
  3.     public Object getValue() {  
  4.         return value;  
  5.     }  
  6.     public void setValue(Object  value) {  
  7.         this.value = value;  
  8.     }  
  9. }  
class Pair {
	private Object value;
	public Object getValue() {
		return value;
	}
	public void setValue(Object  value) {
		this.value = value;
	}
}
再看子類的兩個重寫的方法的型別: [java] view plain copy print ?
  1.        @Override  
  2. public void setValue(Date value) {  
  3.     super.setValue(value);  
  4. }  
  5. @Override  
  6. public Date getValue() {  
  7.     return super.getValue();  
  8. }  
        @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 ?
  1. public static void main(String[] args) throws ClassNotFoundException {  
  2.         DateInter dateInter=new DateInter();  
  3.         dateInter.setValue(new Date());                  
  4.                 dateInter.setValue(new Object());//編譯錯誤  
  5.  }  
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 ?
  1. class Pair {  
  2.     private Date value;  
  3.     public Date getValue() {  
  4.         return value;  
  5.     }  
  6.     public void setValue(Date value) {  
  7.         this.value = value;  
  8.     }  
  9. }  
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子類的位元組碼,結果如下:

[java] view plain copy print ?
  1. class com.tao.test.DateInter extends com.tao.test.Pair<java.util.Date> {  
  2.   com.tao.test.DateInter();  
  3.     Code:  
  4.        0: aload_0  
  5.        1: invokespecial #8                  // Method com/tao/test/Pair."<init>"  
  6. :()V  
  7.        4return  
  8.   
  9.   public void setValue(java.util.Date);  //我們重寫的setValue方法  
  10.     Code:  
  11.        0: aload_0  
  12.        1: aload_1  
  13.        2: invokespecial #16                 // Method com/tao/test/Pair.setValue  
  14. :(Ljava/lang/Object;)V  
  15.        5return  
  16.   
  17.   public java.util.Date getValue();    //我們重寫的getValue方法  
  18.     Code:  
  19.        0: aload_0  
  20.        1: invokespecial #23                 // Method com/tao/test/Pair.getValue  
  21. :()Ljava/lang/Object;  
  22.        4: checkcast     #26                 // class java/util/Date  
  23.        7: areturn  
  24.   
  25.   public java.lang.Object getValue();     //編譯時由編譯器生成的巧方法  
  26.     Code:  
  27.        0: aload_0  
  28.        1: invokevirtual #28                 // Method getValue:()Ljava/util/Date 去呼叫我們重寫的getValue方法  
  29. ;  
  30.        4: areturn  
  31.   
  32.   public void setValue(java.lang.Object);   //編譯時由編譯器生成的巧方法  
  33.     Code:  
  34.        0: aload_0  
  35.        1: aload_1  
  36.        2: checkcast     #26                 // class java/util/Date  
  37.        5: invokevirtual #30                 // Method setValue:(Ljava/util/Date;   去呼叫我們重寫的setValue方法  
  38. )V  
  39.        8return  
  40. }  
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 ?
  1. public ObjectgetValue() {  
  2.         return super.getValue();  
  3.     }  
public ObjectgetValue() {
		return super.getValue();
	}
而子類重寫的方法是: [java] view plain copy print ?
  1. public Date getValue() {  
  2.         return super.getValue();  
  3.     }  
public Date getValue() {
		return super.getValue();
	}
其實這在普通的類繼承中也是普遍存在的重寫,這就是協變。

關於協變:。。。。。。

並且,還有一點也許會有疑問,子類中的巧方法  Object   getValue()和Date getValue()是同 時存在的,可是如果是常規的兩個方法,他們的方法簽名是一樣的,也就是說虛擬機器根本不能分別這兩個方法。如果是我們自己編寫Java程式碼,這樣的程式碼是無法通過編譯器的檢查的,但是虛擬機器卻是允許這樣做的,因為虛擬機器通過引數型別和返回型別來確定一個方法,所以編譯器為了實現泛型的多型允許自己做這個看起來“不合法”的事情,然後交給虛擬器去區別。