Java擦除
概述:
? ?Java泛型在使用過程有諸多的問題,如不存在List<String>.class, List<Integer>不能賦值給List<Number>(不可協變),奇怪的ClassCastException等。 正確的使用Java泛型需要深入的了解Java的一些概念,如協變,橋接方法,以及這篇筆記記錄的類型擦除。Java泛型的處理幾乎都在編譯器中進行,編譯器生成的bytecode是不包涵泛型信息的,泛型類型信息將在編譯處理是被擦除,這個過程即類型擦除。
編譯器如何處理泛型:
通常情況下,一個編譯器處理泛型有兩種方式:
?? ? 1.Code specialization。在實例化一個泛型類或泛型方法時都產生一份新的目標代碼(字節碼or二進制代碼)。例如,針對一個泛型list,可能需要 針對string,integer,float產生三份目標代碼。
?? ? 2.Code sharing。對每個泛型類只生成唯一的一份目標代碼;該泛型類的所有實例都映射到這份目標代碼上,在需要的時候執行類型檢查和類型轉換。
?? ? C++中的模板(template)是典型的Code specialization實現。C++編譯器會為每一個泛型類實例生成一份執行代碼。執行代碼中integer list和string list是兩種不同的類型。這樣會導致代碼膨脹(code bloat),不過有經驗的C++程序員可以有技巧的避免代碼膨脹。
?? ? Code specialization另外一個弊端是在引用類型系統中,浪費空間,因為引用類型集合中元素本質上都是一個指針。沒必要為每個類型都產生一份執行代碼。而這也是Java編譯器中采用Code sharing方式處理泛型的主要原因。
?? ? Java編譯器通過Code sharing方式為每個泛型類型創建唯一的字節碼表示,并且將該泛型類型的實例都映射到這個唯一的字節碼表示上。將多種泛型類形實例映射到唯一的字節碼表示是通過類型擦除(type erasue)實現的。
???類型擦除指的是通過類型參數合并,將泛型類型實例關聯到同一份字節碼上。編譯器只為泛型類型生成一份字節碼,并將其實例關聯到這份字節碼上。類型擦除的關鍵在于從泛型類型中清除類型參數的相關信息,并且再必要的時候添加類型檢查和類型轉換的方法。
?? ??類型擦除可以簡單的理解為將泛型java代碼轉換為普通java代碼,只不過編譯器更直接點,將泛型java代碼直接轉換成普通java字節碼。
?? ??類型擦除的主要過程如下:
?? ? 1.將所有的泛型參數用其最左邊界(最頂級的父類型)類型替換。
?? ? 2.移除所有的類型參數。
?
擦除使我們在泛型代碼內部,無法獲得任何有關參數類型的信息。很蛋疼...
例如:
C++中我們可以這樣寫:
template<typename T> T imax(T a, T b) {T copy; return copy; } class A{};
但是在Java中我們是不能夠生成copy的因為們壓根就不知道T的類型信息。
?
那為什么Java要使用擦除呢?
首先能夠節省空間避免代碼膨脹,主要原因是為了“遷移兼容性”,即允許泛型代碼與非泛型代碼共存,因為泛型是Java后期才添加的為了兼容以前的代碼所以采取了折中的辦法。
那么擦除所帶來的問題我們如何解決呢?
1: 通過引入類型標簽來對擦除進行補償:
class Building{@Overridepublic String toString() {return "Building ...";} } class House extends Building{@Overridepublic String toString() {return "House ...";} }class TestItem<T>{Class<T> type; //通過添加類型標簽,來獲得我要持有的類型的信息public TestItem(Class<T> type) {this.type = type;}public T getInstance() throws InstantiationException, IllegalAccessException {T copy = type.newInstance(); //這樣我就可以利用類型信息進行必要的處理了return copy;} }public class Test {public static void main(String[] args) throws InstantiationException, IllegalAccessException {TestItem<Building> item = new TestItem<>(Building.class);System.out.println(item.getInstance());System.out.println("------------------------");TestItem<House> item2 = new TestItem<>(House.class);System.out.println(item2.getInstance());//出錯,因為我們是利用newInstance來創建對象的,就必須保證我們的對象要有默認的構造方法才行,但是Integer沒有 // System.out.println("------------------------"); // TestItem<Integer> item3 = new TestItem<>(Integer.class); // System.out.println(item3.getInstance());} }上面的Integer的問題,我們可以通過傳入一個工廠來實現
interface Factory<T>{T create(); } class IntegerFactory implements Factory<Integer> {@Overridepublic Integer create() {return new Integer(0);} }class TestItem<T>{Class<T> type; //通過添加類型標簽,來獲得我要持有的類型的信息T copy;Factory<T> factory;public <F extends Factory<T>>TestItem(F factory) {this.factory = factory;}public TestItem(Class<T> type) {this.type = type;}public T getInstance() throws InstantiationException, IllegalAccessException {//copy = type.newInstance(); //這樣我就可以利用類型信息進行必要的處理了copy = factory.create();return copy;} }public class Test {public static void main(String[] args) throws InstantiationException, IllegalAccessException { // TestItem<Building> item = new TestItem<>(Building.class); // System.out.println(item.getInstance()); // System.out.println("------------------------"); // TestItem<House> item2 = new TestItem<>(House.class); // System.out.println(item2.getInstance());//現在把工廠放進去就可以了。System.out.println("------------------------");TestItem<Integer> item3 = new TestItem<>(new IntegerFactory()); System.out.println(item3.getInstance());} }
2: 同樣我們可以通過使用設置擦相互邊界來補償擦除
就像我們在前一篇的比較的時候我們將擦除邊界設定成了Comparable,保證了我們的類新信息是可比較的。
http://www.cnblogs.com/E-star/p/3438226.html
?
注意:
1.虛擬機中沒有泛型,只有普通類和普通方法
2.所有泛型類的類型參數在編譯時都會被擦除
3.創建泛型對象時請指明類型,讓編譯器盡早的做參數檢查(Effective Java,第23條:請不要在新代碼中使用原生態類型)
4.不要忽略編譯器的警告信息,那意味著潛在的ClassCastException等著你。
?
?
轉載于:https://www.cnblogs.com/E-star/p/3438290.html
總結
- 上一篇: VS2012 生成项目报 Lc.exe已
- 下一篇: 菜根谭#8