一、泛型簡介
所謂的泛型,即將類型參數化。主要思想是將算法和數據結構完全分離開,使得一次定義的算法能夠提供多種數據結構使用,從而實現高度可重用的開發。在Java中,可以定義泛型類,泛型接口以及泛型方法。
C++和Java都提供了對泛型的支持,但它們各自處理泛型的方式卻截然不同。C++的編譯器使用Code Specialization的方式——在實例化一個泛型類或泛型方法時都產生一份新的目標代碼(字節碼or二進制代碼)。例如,針對一個泛型list<>,可能需要針對String,Integer分別產生二份不同的目標代碼;而Java的編譯器在處理泛型時採用的是Code Sharing的方式——對每個泛型類只生成唯一的一份目標代碼;該泛型類的所有實例都映射到這份目標代碼上,編譯器會在必要的時候執行類型檢查和類型轉換的工作。
二、類型擦除
JAVA編譯器將多種泛型類形實例映射到唯一的字節碼錶示是通過類型擦除來實現的。類型擦除的關鍵在於從泛型類型中清除類型參數的相關信息,並且再必要的時候添加類型檢查和類型轉換的方法。擦除遵循這樣的原則: 1.所有的有限定邊界的泛型參數用其對應的最左邊界類型來替換;2.無限定的泛型參數使用Object類型來替換。類型T的擦除記爲|T|;參數化類型G<T1, T2, T3, ...>的擦除記爲|G|;嵌套類型T.C的擦除記爲|T|.C;數組類型T[ ] 的擦除記爲|T|[ ]。下面舉幾個簡單的例子加以說明:
public class GenericMemoryCell<AnyTpye> {
private AnyTpye storedValue;
public AnyTpye read(){
return storedValue;
}
public void write(AnyTpye x){
storedValue = x;
}
}
GenericMemoryCell類是個泛型類,AnyType爲其類型,由於AnyType沒有加以任何限定的邊界,故在擦除時使用Object類型類替換,擦除以後結果如下:
public class GenericMemoryCell {
private Object storedValue;
public Object read(){
return storedValue;
}
public void write(Object x){
storedValue = x;
}
}
可以看到,經過擦除以後,代碼內部沒有了任何有關泛型參數的信息,它已經完全丟失。使用下面的代碼來測試上面的泛型類:
GenericMemoryCell<Integer> xx = new GenericMemoryCell<Integer>();
xx.write(3);
int x=xx.read();
使用ByteCode Outline查看對應的字節碼,write方法對應的字節碼如下:ICONST_3
INVOKESTATIC Integer.valueOf(int) : Integer
INVOKEVIRTUAL GenericMemoryCell.write(Object) : void
由write方法的參數爲Object類型可知,該方法實際上將Integer類型的對象作爲Object類型的對象寫入。而對應的read方法調用字節碼如下:
INVOKEVIRTUAL GenericMemoryCell.read() : Object
CHECKCAST Integer
INVOKEVIRTUAL Integer.intValue() : int
可以看到read方法調用實際返回的是個Object類型的對象,編譯器使用“CHECKCAST Integer”來檢測是否能夠將其轉化爲Integer類型,如何可以則執行轉換。否則將拋出ClassCastException異常。由上可知,由於擦除的存在,編譯器在必要的時候必須執行類型檢查和轉換的工作。下面是類型擦除的另一個例子:
public static <T extends BoundingType> T method( T[] a){}
由於類型擦除的存在,且泛型參數T有限定邊界BoudingType,因此擦除之後爲: public static BoudingType method(BoudingType[] a){}
三、使用泛型的限制
由於擦除的存在,因此在使用java的泛型時應該特別小心。在泛型代碼內部,我們無法獲取有關泛型參數的任何信息,因此任何運行時需要知道確切類型信息的操作都無法完成,以下是使用時候的一些限制:1.不能實例化類型變量,不能創建泛型數組對象。也就是說不能使用new T() , new T[ ] , 或者T.class這樣的表達式。因爲T可能由Object或者其邊界代替,因此這樣的調用是沒有意義的。
2.在一個泛型類中,static方法或者static域均不可引用類的類型變量。
3.不能實例化參數化類型的數組,即GenericMemoryCell <String> [ ] arr = new GenericMemoryCell <String> [10 ] ; 該操作是非法的。
4.不能使用基本數據類型來實例化類型參數,應該使用其對應的包裝類;即 GenericMemoryCell <int> 是不合法的。
5.不能使用instanceof表達式,instanceof檢測和類型轉換工作只對原始類型有效。
諸如以上的限制等,都是由於擦除引起的,應該特別小心對待。