Java泛型解析(03):虛擬機器執行泛型代碼,java03
Java泛型解析(03):虛擬機器執行泛型代碼
Java虛擬機器是不存在泛型型別對象的,所有的對象都屬於普通類,甚至在泛型實現的早起版本中,可以將使用泛型的程式編譯為在1.0虛擬機器上能夠啟動並執行class檔案,這個向後相容性後期被拋棄了,所以後來如果用Sun公司的編譯器編譯的泛型代碼,是不能運行在Java5.0之前的虛擬機器的,這樣就導致了一些實際生產的問題,如一些遺留代碼如何跟新的系統進行銜接,要弄明白這個問題,需要先瞭解一下虛擬機器是怎麼執行泛型代碼的。 虛擬機器的一種機制:擦除型別參數,並將其替換成特定類型,沒有指定特定類型用Object代替,如前文中的Couple<T>類,虛擬機器擦除後: [code01]
public class Couple { private Object wife ; private Object husband ; public Couple(Object wife, Object husband) { this.wife = wife; this.husband = husband; } public void setWife(Object wife) {this. wife = wife;} public void setHusband(Object husband) {this. husband = husband;} public Object getWife() {return wife;} public Object getHusband() {return husband;} } 型別參數T是一個任意類型的,所以擦除後用Object代替了。不管是Couple<Employee>或者Couple<String>擦除後都成為了原始類Couple類,這就好比回到了泛型引入Java之前的普通類。所以這裡重點圍繞著
擦除型別參數這個機制展開講解。 如有對型別參數有類型限定會怎麼替換呢?擦除型別參數機制告訴我們,使用限定的類型代替,如果有多個,使用第一個代替,看一段代碼:
[code02]
public class Period<T extends Comparable<T> & Serializable> { private T begin; private T end; public Period(T one, T two) { if (one.compareTo(two) > 0) {begin = two;end = one; } else {begin = one;end = two;} } } code02擦除後,Period的原始類型如下:
[code03]
public class Period { private Comparable begin; private Comparable end; public Period(Comparable one, Comparable two) { if (one.compareTo(two) > 0) {begin = two; end = one; } else {begin = one; end = two;} } } 思考一下,如果將Period<T
extends Comparable<T> & Serializable>寫成Period<T
extends Serializable & Comparable<T>>會是怎麼樣呢?同理,擦除後原始類型用第一個Serializable代替,這樣進行compareTo方法調用的時候,編譯器會進行必要的強制類型轉換,所以為了提高效率,將標籤介面(沒有任何方法的介面,也叫tagging介面)放在後面。 先來看看虛擬機器執行運算式的時候發生了什麼,如:[code04]
Couple<Employee> couple = ...; Employee wife = couple.getWife();
擦除後,getWife()返回的是Object類型,然後虛擬機器會插入強制類型轉換,將Object轉換為Employee,所以虛擬機器實際上執行了兩天指令: 1.調用Couple.getWife()方法。 2.將Object轉換成Employee類型。
再來看看虛擬機器執行泛型方法的時候發生了什麼,泛型方法如:
[code05]
public static <T extends Comparable<T>> max(T[] arrays) {... }擦除後成了:public staticComoparable max(Comparable[] arrays) {... }
但是泛型方法的擦除會帶來兩個複雜的問題,且看第一個執行個體,一個執行個體:[code06]
public class Period <T extends Comparable<T> & Serializable> { private T begin; private T end; public Period(T one, T two) { if (one.compareTo(two) > 0) {begin = two;end = one; } else {begin = one;end = two;} } public void setBegin(T begin) {this. begin = begin;} public void setEnd(T end) {this. end = end;} public T getBegin() {return begin;} public T getEnd() {return end;} } public class DateInterval extends Period<Date> { public DateInterval(Date one, Date two) { super(one, two); } public void setBegin(Date begin) { super.setBegin(begin); } }
DateInterval類型擦除後,Period中的方法變成:
public void setBegin(Object begin) {...}
而DateInterval中的方法還是:
public void setBegin(Date begin) {...} 所以DateInterval從Period中繼承了
public
void setBegin(Object begin) {...}而自身又存在
public
void setBegin(Date begin) {...}方法,使用者使用時問題發生了:
[code07]
Period<Date> period = new DateInterval(...); period.setBegin(new Date());
這裡因為period引用指向了DateInterval執行個體,根據多態性,setBegin應該調用DateInterval對象的setBegin方法,可是這個擦除讓Period中的
public
void setBegin(Object begin) {...}被調用,導致了擦除與多態發生了衝突,怎麼辦呢?虛擬機器此時會在DateInterval類中產生一個橋方法(bridge method),調用過程發生了細微的變化:
[code08]
public void setBegin(Object begin) { setBegin((Date)begin); }
有了這個合成的橋方法以後,code07中對setBegin的調用步驟如下: 1.調用DateInterval.setBegin(Object)方法。 2.DateInterval.setBegin(Object)方法調用DateInterval.setBegin(Date)方法。 發現了嗎,當我們在DateInterval中增加了getBegin方法之後會是什麼樣子的呢?是不是Peroid中有一個Object getBegin()的方法,而DateInterval中有一個Date getBegin()方法呢,這兩個方法在Java中是不能同時存在的?可是Java5以後增加了一個協變類型,使得這裡是被允許的,看看DateInterval中getBegin方法就知道了:[code09]
@Override public Date getBegin(){ return super.getBegin(); } 這裡用了@Override,說明是覆蓋了父類的Object getBegin()方法,而返回值可以指定為父類中的返回值類型的子類,這就是協變類型,這是Java5以後才可以允許的,允許子類覆蓋了方法後指定一個更嚴格的類型(子類型)。
總結: 1.記住一點,虛擬機器中沒有泛型,只有普通的類。 2.所有泛型的型別參數都用它們限定的類型代替,沒有限定則用Object。 3.為了保持型別安全,虛擬機器在有必要時插入強制類型轉換。 4.橋方法的合成用來保持多態性。 5.協變類型允許子類覆蓋方法後返回一個更嚴格的類型。
Java泛型解析(01):認識泛型
Java泛型解析(02):萬用字元限定
Java泛型解析(03):虛擬機器執行泛型代碼
java泛型完成數組問題,剛學泛型,協助
整數數組的順序收縮
public static int seqSearch(int[] arr, int first, int last, int target) {
for (int i = first; i < last; i++)
if (arr[i] == target)
return i;
return -1;
}
JAVA中list的泛型問題
是的
上面是ArrayList的get方法,返回時是強轉了
如果不指定泛型就是Object類型的,如果指定了泛型,類型不對的話你put都put不進去的