java和其他語言一樣,都支持泛型,包括泛型類和泛型方法,但是java的泛型比較特殊。因為java的泛型並不是在java誕生之初就加入的,在很長的一段時間里,java是沒有泛型的,在需要泛型的地方,統統都采用協變的方式,也就是采用Object,比如ArrayList類,元素的類型就是Object。為了兼容原來的代碼,java的設計者希望在加入泛型之后,所有的泛型都可以傳給原來的對應的非泛型參數,例如,可以把ArrayList<xxx>傳給原來接收ArrayList參數的方法。為了達到這個目的,java的設計者采取了一種叫做“類型擦除”的實現方式,把泛型攔截在了編譯階段,在虛擬機中,所有的泛型參數最終的類型都會是轉換后的Object類型(如果有類型限定,則是限定后的類型,如 <T extends Super>,則泛型T在虛擬機中統一都是Super類型),然后利用協變性而兼容所有類型或限定類型參數的傳入,從而實現泛型。
比如用戶定義泛型類:
class Super<T,V>{ T a; public V mehod(T arv){ a=arv; return a; } }
在經過編譯階段的類型擦除之后,虛擬機看到的Super類是這樣的:
class Super{ Object a; public Object mehod(Object arv){ a=arv; return a; } }
而且可以看到,無論有多少個實例化后的泛型,比如Super<String,String>、Super<Interger,Double>,在虛擬機中,都只存在一個Super類。
在應用泛型類的時候,如果返回值也是泛型,那么返回的將是Object,但是很顯然,我們在使用泛型的過程中,並沒有要顯示的進行類型轉換,比如我們不需要這樣 String a=(String) super.method("hahaha"); 而是直接 String a= super.method("hahaha"); 就可以了。為什么呢?
則是因為,為了返回實現類型的自動匹配,java編譯器會在“類型擦除”的基礎上在進行“轉換插入”操作。每當使用泛型的返回值時,編譯器會制動插入類型轉換,所以原始代碼是 String a= super.method("hahaha"); ,編譯之后,虛擬機看到的卻是 String a= (String)super.method("hahaha"); ,類型轉換被“插入”了。
因為類型擦除后,原有的泛型方法的泛型參數都被替換成了Object,因此無法實現方法覆蓋,但實際應用中,肯定需要實現方法覆蓋從而達到多態的目的。
比如有用戶繼承了Super<T,V>這個泛型,實現了子類SubClass:Super<String,String>:
class SubClass:Super<String,String>{ String a; public String mehod(String arv){ a=arv; return a; } }
因為用戶只實現了方法 public String mehod(String arv) ,而父類的method方法是 public Object mehoed(Object arv) ,因此覆蓋失敗。為了實現對method方法的覆蓋,編譯器在進行編譯時,自動插入了一個橋接方法,橋接方法形式如下:
public Object mehoed(Object arv){ return method((String) arv); }
橋接方法的方法簽名和返回值和父類的泛型方法 public Object mehoed(Object arv) 一致,因此可以覆蓋父類的method方法,實現了多態。通過橋接方法,還實現了對用戶定義的method方法的調用,在程序員看來,就好像 public String mehoed(String arv) 成功覆蓋了 public Object mehoed(Object arv) 一樣。
正因為java是以這種比較奇怪的方式實現了泛型,因此一切new T(..)或者new T[]其實都會是new Object,沒有任何意義,所以java干脆在編譯階段就禁止了這種語法,也就是說,泛型只能傳入,不能產生。
除了不能產生泛型外,其它場合泛型和具體類型無差別,泛型參數可以用於類型轉換、方法參數的類型等各種場合,比如這樣 (T)a; 也是合法的。同時我們也可以發現,泛型參數的實例化,其實只是在編譯階段傳入了類型信息,這個類型信息被用於“插入轉換”和“方法橋接”。
但是需要注意的是,很多看起來像產生泛型的語法,其實並不是真正產生泛型,比如 List<String>=new ArrayList<String>() ,這里產生的只有一個ArrayList,並且這個ArrayList的類型參數被傳入了一個String,這個String將用於ArrayList的“橋接方法”、“轉換插入”等用處。實際上,在ArrayList<>的內部,是以一個Object[ ]來存放數據的,而不是String[ ],在你add("hahaha")的時候,這個泛型還是被傳入的。在get(0)時,這個被傳入的類型String在“轉換插入”中發揮了作用,每次調用 get(0); 都被轉換成了 (String)get(0); 。
上面說泛型在編譯階段就被“擦除”了,其實也不完全對,利用反射,仍然可以獲取泛型信息,比如泛型參數、泛型限制、通配符信息等都可以獲取到,說明在java的字節碼(即.class文件)中,泛型信息仍然存在。因此,更准確的說,是泛型經過類加載器加載到虛擬機之后,泛型比擦除了,但是泛型的信息仍然存在字節碼中,並且可以通過反射獲取到。