Java™ 教程(泛型的限制)

泛型的限制

要有效地使用Java泛型,必須考慮如下限制:segmentfault

  • 沒法使用基元類型實例化泛型類型
  • 沒法建立類型參數的實例
  • 沒法聲明類型爲類型參數的靜態字段
  • 沒法對參數化類型使用強制類型轉換或instanceof
  • 沒法建立參數化類型的數組
  • 沒法建立、捕獲或拋出參數化類型的對象
  • 沒法重載將每一個重載的形式參數類型擦除爲相同原始類型的方法

沒法使用基元類型實例化泛型類型

考慮如下參數化類型:數組

class Pair<K, V> {

    private K key;
    private V value;

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    // ...
}

建立Pair對象時,不能將基本類型替換爲類型參數KVapp

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

你只能將非基本類型替換爲類型參數KVui

Pair<Integer, Character> p = new Pair<>(8, 'a');

請注意,Java編譯器將8自動裝箱到Integer.valueOf(8),將'a'自動裝箱到Character('a')this

Pair<Integer, Character> p = new Pair<>(Integer.valueOf(8), new Character('a'));

有關自動裝箱的詳細信息,請參閱自動裝箱和拆箱code

沒法建立類型參數的實例

你沒法建立類型參數的實例,例如,如下代碼致使編譯時錯誤:對象

public static <E> void append(List<E> list) {
    E elem = new E();  // compile-time error
    list.add(elem);
}

做爲解決方法,你能夠經過反射建立類型參數的對象:get

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);

沒法聲明類型爲類型參數的靜態字段

類的靜態字段是類的全部非靜態對象共享的類級變量,所以,類型參數的靜態字段是不容許的,考慮如下類:string

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) { }
}

重載將共享相同的類文件表示,並將生成編譯時錯誤。


上一篇:類型擦除

下一篇:建立和使用包

相關文章
相關標籤/搜索