類型擦除
正確理解泛型概念的首要前提是理解類型擦除(type erasure)。 Java中的泛型基本上都是在編譯器這個層次來實現的。在生成的Java字節代碼中是不包含泛型中的類型信息的。使用泛型的時候加上的類型參數,會被編譯器在編譯的時候去掉。這個過程就稱為類型擦除。如在代碼中定義的List<Object>和List<String>等類型,在編譯之后都會變成List。JVM看到的只是List,而由泛型附加的類型信息對JVM來說是不可見的。Java編譯器會在編譯時盡可能的發現可能出錯的地方,但是仍然無法避免在運行時刻出現類型轉換異常的情況。類型擦除也是Java的泛型實現方式與C++模板機制實現方式之間的重要區別。
很多泛型的奇怪特性都與這個類型擦除的存在有關,包括:
泛型類并沒有自己獨有的Class類對象。比如并不存在List<String>.class或是List<Integer>.class,而只有List.class。
靜態變量是被泛型類的所有實例所共享的。對于聲明為MyClass<T>的類,訪問其中的靜態變量的方法仍然是 MyClass.myStaticVar。不管是通過new MyClass<String>還是new MyClass<Integer>創建的對象,都是共享一個靜態變量。
泛型的類型參數不能用在Java異常處理的catch語句中。因為異常處理是由JVM在運行時刻來進行的。由于類型信息被擦除,JVM是無法區分兩個異常類型MyException<String>和MyException<Integer>的。對于JVM來說,它們都是 MyException類型的。也就無法執行與異常對應的catch語句。
類型擦除的基本過程也比較簡單,首先是找到用來替換類型參數的具體類。這個具體類一般是Object。如果指定了類型參數的上界的話,則使用這個上界。把代碼中的類型參數都替換成具體的類。同時去掉出現的類型聲明,即去掉<>的內容。比如T get()方法聲明就變成了Object get();List<String>就變成了List。接下來就可能需要生成一些橋接方法(bridge method)。這是由于擦除了類型之后的類可能缺少某些必須的方法。比如考慮下面的代碼:
class MyString implements Comparable<String> { public int compareTo(String str) { return 0; }}
當類型信息被擦除之后,上述類的聲明變成了class MyString implements Comparable。但是這樣的話,類MyString就會有編譯錯誤,因為沒有實現接口Comparable聲明的int compareTo(Object)方法。這個時候就由編譯器來動態生成這個方法。
通配符
private <E extends View> E $(int id) {
E e = (E) findViewById(id);
return e;
}
Iterable<? extends JavaFileObject> fileObjects = Arrays.asList(sourceObject);