Java™ 教程(泛型的限制)

博弈發表於2019-01-19

泛型的限制

要有效地使用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物件時,不能將基本型別替換為型別引數KV

Pair<int, char> p = new Pair<>(8, `a`);  // compile-time error

你只能將非基本型別替換為型別引數KV

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是由phonepagerpc共享的,所以os的實際型別是什麼?它不能同時是SmartphonePagerTabletPC,因此,你無法建立型別引數的靜態欄位。

無法對引數化型別使用強制型別轉換或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) { }
}

過載將共享相同的類檔案表示,並將生成編譯時錯誤。


上一篇:型別擦除

下一篇:建立和使用包

相關文章