摘要:靜態變量是被泛型類的所有實例所共享的。對于這個類型系統,有如下的一些規則相同類型參數的泛型類的關系取決于泛型類自身的繼承體系結構。在代碼中避免泛型類和原始類型的混用。參考泛型類型擦除
Java泛型總結
Java泛型是JDK5引入的一個新特性,允許在定義類和接口的時候使用類型參數(type parameter)。聲明的類型參數在使用的時候使用具體的類型來替換。泛型最主要的應用是在JDK5中的新集合類框架中。對于泛型概念的引入,開發社區的觀點是褒貶不一。從好的方面上說,泛型的引入可以解決之前的集合類框架在使用過程中通常會出現的運行時刻類型錯誤,因為編譯器可以在編譯時刻就發現很多明顯的錯誤。從不好的方面說,為了保證與舊版本的兼容性,Java泛型的實現上還存在著不夠優雅的地方。
類型擦除正確理解泛型概念的首要前提是理解類型擦除(type erasure)。Java中的泛型基本上都是在編譯器這個層次來實現的。在生成的Java字節碼中是不包含泛型中的類型信息的。使用泛型的時候加上的類型參數,會被編譯器在編譯的時候去掉。這個過程就稱為類型擦除。比如在代碼中定義的List和List
通過如下代碼片段感受類型擦除:
ArrayLista1 = new ArrayList<>(); ArrayList a2 =new ArrayList<>(); Class c1 =a1.getClass(); Class c2 = a2.getClass(); System.out.println(c1.equals(c2)); //Output: true
此時,程序輸出true,這就是類型擦除造成的。因為不管是ArrayList
Listlist = new ArrayList<>(); Map map = new HashMap<>(); System.out.println(Arrays.toString(list.getClass().getTypeParameters())); //[E] System.out.println(Arrays.toString(map.getClass().getTypeParameters())); //[K, V]
我們期望的是返回泛型參數的類型,結果返回的僅僅是參數的占位符。
public staticT[] makeArray(){ return new T[10]; //編譯期報錯:不能創建泛型類型的數組 }
因為T僅僅是個占位符,并不具有真實的類型信息。為了解決這個問題,可以利用反射:
public staticT[] makeArray(Class clazz) { return (T[]) Array.newInstance(clazz, 10); }
很多泛型的奇怪特性都與類型擦除的存在有關,包括:
泛型類并沒有自己獨有的Class類對象。比如并不存在List
靜態變量是被泛型類的所有實例所共享的。對于聲明為MyClass
泛型的類型參數不能用在Java異常處理的catch語句中。因為異常處理是由JVM在運行時刻來進行的。由于類型信息被擦除,JVM是無法區分兩個異常類型MyException
當執行類型擦除時,首先是找到用來替換類型參數的具體類。這個具體類一般是Object。如果指定了類型參數的上界的話,則使用這個上界。把代碼中的類型參數都替換成具體的類。同時去掉出現的類型聲明,即去掉<>的內容。比如T get()方法聲明就變成了Object get();List
class MyString implements Comparable{ public int compareTo(String str) { return 0; } }
當類型信息被擦除之后,上述類的聲明變成了class MyString implements Comparable。但是這樣類MyString就會有編譯錯誤,因為沒有實現接口Comparable聲明的compareTo(Object)方法。這個時候就由編譯器來動態生成這個方法。
實例分析了解類型擦除機制之后,就會明白編譯器承擔了全部的類型檢查工作。編譯器禁止某些泛型的使用方式,正是為了確保類型的安全性。以List和List
public void inspect(List
這段代碼中,inspect方法接受List作為參數,當在test方法中試圖傳入List
在使用泛型類的時候,既可以指定一個具體的類型,如List
public void wildcard(List> list) { list.add(1); //編譯錯誤 }
如上所示,試圖對一個帶通配符的泛型類進行操作的時候,總是會出現編譯錯誤。其原因在于通配符所表示的類型是未知的。
因為對于List>中的元素只能用Object來引用,在有些情況下不是很方便。在這些情況下,可以使用上下界來限制未知類型的范圍。如List extends Number>說明List中包含的是Number及其子類。而List super Number>則說明List中包含的是Number及其父類。當引入了上界時候,在使用類型的時候就可以使用上界類中定義的方法。比如訪問List extends Number>的時候,就可以使用Number類的intValue等方法。
類型系統在Java中,比較常見的是通過繼承機制而產生的類型體系結構。比如String繼承自Object。根據Liskov替換原則,子類是可以替換父類的。當需要Object類的引用的時候,如果傳入一個String對象是沒有任何問題的。但是反過來的話,即用父類的引用替換子類引用時,就需要進行強制類型轉換。編譯器并不能保證運行時刻的這種轉換一定是合法的。這種自動的子類替換父類的轉換機制,對于數組也是適用的。String[]可以替換Object[]。但是泛型的引入,對于這個類型系統產生了一定的影響。例如List
引入泛型之后的類型系統增加了兩個維度:一個是類型參數自身的繼承體系結構,另外一個是泛型類或接口自身的繼承體系結構。第一個指的是對于List
相同類型參數的泛型類的關系取決于泛型類自身的繼承體系結構。即List
當泛型類的類型聲明中使用了通配符的時候,其子類可以在兩個維度上分別展開。如對Collection extends Number>來說,其子類型可以在Collection這個維度上展開,即List extends Number>和Set extends Number>等;也可以在Number這個維度展開,即Collection
如果泛型類中包含多個類型參數,則對每個類型參數分別應用上面的規則。
因此,對于上面錯誤的代碼,只需要將List修正為List>即可。List
泛型類與一般的Java類基本相同,只是在類和接口定義上多出來了用<>聲明的類型參數。一個類可以有多個類型參數,比如MyClass
class ClassTest{ private X x; private static Y y; //編譯錯誤,不能用在靜態變量中 public X getFirst() { return x; //正確用法 } public void wrong() { Z z = new Z(); //編譯錯誤,不能查創建對象 } }
假設允許類型參數聲明為靜態屬性,那么如下代碼將會非常混亂。
public class Computer{ private static T os; public Computer(T os) { this.os = os; } public T getOS() { return os; } public static void main(String [] args) { Computer c1 = new Computer<>(); Computer c2 = new Computer<>(); Computer c3 = new Computer<>(); System.out.println(c1.getOS()); System.out.println(c2.getOS()); System.out.println(c3.getOS()); } }
因為os為Computer類的靜態屬性,所以c1,c2,c3這3個Computer實例共享這個屬性,那么此時os的類型是什么?因此,不允許聲明靜態的類型參數屬性。
總結在使用Java泛型的時候可以遵循一些基本的原則,從而避免一些常見的問題。
在代碼中避免泛型類和原始類型的混用。比如List
在使用帶通配符的泛型類的時候,需要明確通配符所代表的一組類型的概念。由于具體的類型是未知的,很多操作是不允許的。
泛型類最好不要同數組一塊兒使用。只能創建new List>[10]這樣的數組,無法創建new List
InfoQ
Java泛型:類型擦除
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/66092.html
摘要:知識點總結泛型知識點總結泛型泛型泛型就是參數化類型適用于多種數據類型執行相同的代碼泛型中的類型在使用時指定泛型歸根到底就是模版優點使用泛型時,在實際使用之前類型就已經確定了,不需要強制類型轉換。 Java知識點總結(Java泛型) @(Java知識點總結)[Java, Java泛型] [toc] 泛型 泛型就是參數化類型 適用于多種數據類型執行相同的代碼 泛型中的類型在使用時指定 泛...
摘要:然而中的泛型使用了類型擦除,所以只是偽泛型。總結本文介紹了泛型的使用,以及類型擦除相關的問題。一般情況下泛型的使用比較簡單,但是某些情況下,尤其是自己編寫使用泛型的類或者方法時要注意類型擦除的問題。 簡介 Java 在 1.5 引入了泛型機制,泛型本質是參數化類型,也就是說變量的類型是一個參數,在使用時再指定為具體類型。泛型可以用于類、接口、方法,通過使用泛型可以使代碼更簡單、安全。然...
摘要:總結數組與泛型的關系還是有點復雜的,中不允許直接創建泛型數組。本文分析了其中原因并且總結了一些創建泛型數組的方式。 簡介 上一篇文章介紹了泛型的基本用法以及類型擦除的問題,現在來看看泛型和數組的關系。數組相比于Java 類庫中的容器類是比較特殊的,主要體現在三個方面: 數組創建后大小便固定,但效率更高 數組能追蹤它內部保存的元素的具體類型,插入的元素類型會在編譯期得到檢查 數組可以持...
摘要:知識點總結反射反射操作泛型知識點總結反射采用泛型擦除的機制來引入泛型。中的泛型僅僅是給編譯器使用的,確保數據的安全性和免去強制類型轉換的麻煩。 Java知識點總結(反射-反射操作泛型) @(Java知識點總結)[Java, 反射] Java采用泛型擦除的機制來引入泛型。Java中的泛型僅僅是給編譯器javac使用的, 確保數據的安全性和免去強制類型轉換的麻煩 。但是,__一旦編譯完成,...
簡介 前兩篇文章介紹了泛型的基本用法、類型擦除以及泛型數組。在泛型的使用中,還有個重要的東西叫通配符,本文介紹通配符的使用。 這個系列的另外兩篇文章: Java 泛型總結(一):基本用法與類型擦除 Java 泛型總結(二):泛型與數組 數組的協變 在了解通配符之前,先來了解一下數組。Java 中的數組是協變的,什么意思?看下面的例子: class Fruit {} class Apple ex...
閱讀 2034·2021-11-11 16:54
閱讀 2111·2019-08-30 15:55
閱讀 3611·2019-08-30 15:54
閱讀 391·2019-08-30 15:44
閱讀 2228·2019-08-30 10:58
閱讀 424·2019-08-26 10:30
閱讀 3048·2019-08-23 14:46
閱讀 3191·2019-08-23 13:46