泛型的限制
要有效地使用Java泛型,必須考慮以下限制:
- 無法使用基元型別例項化泛型型別
- 無法建立型別引數的例項
- 無法宣告型別為型別引數的靜態欄位
- 無法對引數化型別使用強制型別轉換或
instanceof
- 無法建立引數化型別的陣列
- 無法建立、捕獲或丟擲引數化型別的物件
- 無法過載將每個過載的形式引數型別擦除為相同原始型別的方法
無法使用基元型別例項化泛型型別
考慮以下引數化型別:
class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
// ...
}
建立Pair
物件時,不能將基本型別替換為型別引數K
或V
:
Pair<int, char> p = new Pair<>(8, `a`); // compile-time error
你只能將非基本型別替換為型別引數K
和V
:
Pair<Integer, Character> p = new Pair<>(8, `a`);
請注意,Java編譯器將8
自動裝箱到Integer.valueOf(8)
,將`a`
自動裝箱到Character(`a`)
:
Pair<Integer, Character> p = new Pair<>(Integer.valueOf(8), new Character(`a`));
有關自動裝箱的詳細資訊,請參閱自動裝箱和拆箱。
無法建立型別引數的例項
你無法建立型別引數的例項,例如,以下程式碼導致編譯時錯誤:
public static <E> void append(List<E> list) {
E elem = new E(); // compile-time error
list.add(elem);
}
作為解決方法,你可以通過反射建立型別引數的物件:
public static <E> void append(List<E> list, Class<E> cls) throws Exception {
E elem = cls.newInstance(); // OK
list.add(elem);
}
你可以按如下方式呼叫append
方法:
List<String> ls = new ArrayList<>();
append(ls, String.class);
無法宣告型別為型別引數的靜態欄位
類的靜態欄位是類的所有非靜態物件共享的類級變數,因此,型別引數的靜態欄位是不允許的,考慮以下類:
public class MobileDevice<T> {
private static T os;
// ...
}
如果允許型別引數的靜態欄位,則以下程式碼將混淆:
MobileDevice<Smartphone> phone = new MobileDevice<>();
MobileDevice<Pager> pager = new MobileDevice<>();
MobileDevice<TabletPC> pc = new MobileDevice<>();
因為靜態欄位os
是由phone
、pager
和pc
共享的,所以os
的實際型別是什麼?它不能同時是Smartphone
、Pager
和TabletPC
,因此,你無法建立型別引數的靜態欄位。
無法對引數化型別使用強制型別轉換或instanceof
因為Java編譯器會擦除泛型程式碼中的所有型別引數,所以無法驗證在執行時使用泛型型別的引數化型別:
public static <E> void rtti(List<E> list) {
if (list instanceof ArrayList<Integer>) { // compile-time error
// ...
}
}
傳遞給rtti
方法的引數化型別集是:
S = { ArrayList<Integer>, ArrayList<String> LinkedList<Character>, ... }
執行時不跟蹤型別引數,因此它無法區分ArrayList<Integer>
和ArrayList<String>
之間的區別,你可以做的最多是使用無界萬用字元來驗證列表是否為ArrayList
:
public static void rtti(List<?> list) {
if (list instanceof ArrayList<?>) { // OK; instanceof requires a reifiable type
// ...
}
}
通常,除非通過無界萬用字元對其進行引數化,否則無法強制轉換為引數化型別,例如:
List<Integer> li = new ArrayList<>();
List<Number> ln = (List<Number>) li; // compile-time error
但是,在某些情況下,編譯器知道型別引數始終有效並允許強制轉換,例如:
List<String> l1 = ...;
ArrayList<String> l2 = (ArrayList<String>)l1; // OK
無法建立引數化型別的陣列
你無法建立引數化型別的陣列,例如,以下程式碼無法編譯:
List<Integer>[] arrayOfLists = new List<Integer>[2]; // compile-time error
以下程式碼說明了將不同型別插入到陣列中時會發生什麼:
Object[] strings = new String[2];
strings[0] = "hi"; // OK
strings[1] = 100; // An ArrayStoreException is thrown.
如果你使用泛型列表嘗試相同的操作,則會出現問題:
Object[] stringLists = new List<String>[]; // compiler error, but pretend it`s allowed
stringLists[0] = new ArrayList<String>(); // OK
stringLists[1] = new ArrayList<Integer>(); // An ArrayStoreException should be thrown,
// but the runtime can`t detect it.
如果允許引數化列表陣列,則前面的程式碼將無法丟擲所需的ArrayStoreException
。
無法建立、捕獲或丟擲引數化型別的物件
泛型類不能直接或間接擴充套件Throwable
類,例如,以下類將無法編譯:
// Extends Throwable indirectly
class MathException<T> extends Exception { /* ... */ } // compile-time error
// Extends Throwable directly
class QueueFullException<T> extends Throwable { /* ... */ // compile-time error
方法無法捕獲型別引數的例項:
public static <T extends Exception, J> void execute(List<J> jobs) {
try {
for (J job : jobs)
// ...
} catch (T e) { // compile-time error
// ...
}
}
但是,你可以在throws
子句中使用型別引數:
class Parser<T extends Exception> {
public void parse(File file) throws T { // OK
// ...
}
}
無法過載將每個過載的形式引數型別擦除為相同原始型別的方法
一個類不能有兩個在型別擦除後具有相同的簽名的過載方法。
public class Example {
public void print(Set<String> strSet) { }
public void print(Set<Integer> intSet) { }
}
過載將共享相同的類檔案表示,並將生成編譯時錯誤。