1. 程式人生 > >Java泛型的約束和侷限性

Java泛型的約束和侷限性

在使用Java泛型時需要考慮一些限制, 大多數限制都是由於型別擦除所引起的。

1.不能使用基本型別例項化型別引數

型別引數 T不能取8中基本型別,需要的時候採用包裝器型別,如果不能接受這樣的替換時, 可以使用獨立的類和方法來處理。

2.執行時的型別查詢只會產生原始型別(以instanceof和getClass為例)

    if(a instanceof Pair<String>)                  //error
    if(a instanceof Pair<?>)
    if(a instanceof Pair)                          //只能檢查是否是一個Pair
Pair<String> stringPair = ....; Pair<Employee> empPair = ...; if(stringPair.getClass() == empPair.getClass())//equal,返回Pair.class

3.不能建立引數化型別的陣列

不能建立泛型陣列,想要收集泛型物件, 只有一種安全有效的方法, 例如:

    pair<String>[] table = new Pair<String>[10];   //error
    List list = new ArrayList<Pair<String
>>();
    String[] table = new String[10];               //普通陣列
    Object[] oArray = table;                       //賦給父類       
    table[1] = 1;                                  //編譯不通過,不能轉換

    Pair<String>[] table = new Pair<String>[10];   //不可以,假設可以
    //擦除以後, table的型別是Pair[]。可以將其轉換成Object[]                                                                                       
Object[] oArray = table; table[0] = new Pair<Employee>(); //由於擦除可以通過陣列儲存檢查,但仍然會導致型別錯誤, 所以不允許
note:只是不允許建立泛型陣列, 而宣告變數是合法的,只不過不能初始化。想要初始化可以採用下面的形式(陣列的型別不可以是型別變數,而採用萬用字元的方式), 不過結果不安全, 因為隨時可能產生型別錯誤。
    Pair<String>[] table = (Pair<String>[]) new Pair<?>[10];//合法

4.Varargs警告

    public static <T> void addAll(Collection<T> coll, T...ts) {
        for(T t : ts) coll.add(t);
    }

    Collection<Pair<String>> table = new HashSet<>();
    Pair<String> pair1 = new Pair<>();
    Pair<String> pair2 = new Pair<>();
    addAll(table, pair1, pair2);
    //code3
    @SafeVarargs
    public static <T> void addAll(Collection<T> coll, T... ts)

這裡的引數ts事實上是一個裝有提供引數的陣列,這裡放寬了規則。關於變長泛型引數的傳入,有警告不過不會報錯。可以對僅僅讀取泛型陣列元素的方法使用code3中的Annotation。

5.不能例項化型別變數

    public Pair() { first = new T(); second = new T(); }   // Error

因為型別擦除會把T改變成Object, 顯而易見new Object()沒有什麼意義,所以不能例項化型別變數。在Java8中採用構造器表示式來解決這個問題,如下例所示:

    Pair<String> p = Pair.makePair(String::new);
    public static <T> Pair<T> makePair(Supplier<T> constr) {
        return new Pair<>(constr.get(), constr.get());
}

6.不能構造一個泛型陣列

    public static <T extends Comparable> T[] minmax(T[] a) { T[] mm = new T[2]; . . . } // Error

型別擦除將總會使得這個方法構造一個數組Comparable[2].
事實上,如果想使用的這樣一個數組只是被當做一個類的私有域來使用, 可以通過將這樣的陣列宣告為Object[], 並在查詢元素時使用強制型別轉換。例如ArrayList<E>可以這樣實現:

    public class ArrayList<E> {
        private Object[] elements;
        . . .
        @SuppressWarnings("unchecked") public E get(int n) { return (E) elements[n]; }
        public void set(int n, E e) { elements[n] = e; } // no cast needed
        }

7.型別變數不能出現在泛型類的靜態上下文中

    public class Singleton<T> {
        private static T singleInstance;          // Error
        public static T getSingleInstance() {     // Error
            if (singleInstance == null) 
                construct new instance of T
        return singleInstance;
    }
}

泛型類上的泛型型別在例項化時確定。 比如:

Singleton<String> singletonForString= new Singleton(); 
Singleton<Boolean> singletonForBoolean= new Singleton(); 

但靜態成員是被該類的所有物件共享的,所以泛型型別不能應用到靜態成員上。

8.不能丟擲或捕獲泛型類例項

Java裡面既不能丟擲也不能捕獲泛型類異常。
1.泛型類不能extends Throwable。

    public class Problem<T> extends Exception { /* . . . */ } // Error

2.不可以在catch中使用型別變數,例如:

    public static <T extends Throwable> void doWork(Class<T> t) {
        try {
            System.out.println();
        } catch (T e) {// Error--can't catch type variable
            e.printStackTrace();
        }
    }

3.在異常規範中是可以中使用泛型的,例如:

    public static <T extends Throwable> void doWork(T t) throws T {
    try {
        ...;
    } catch (Throwable realCause) {
        t.initCause(realCause);
        throw t;
    }
}

9.注意型別擦除後的衝突

要注意到在型別擦除後的衝突,例如:

    public class Pair<T> {
        public boolean equals(T value) { 
            return first.equals(value) && second.equals(value); 
        }
        . . .
}

對於Pair<String>,就存在兩個方法:

    boolean equals(String) // defined in Pair<T>
    boolean equals(Object) // inherited from Object

擦除後的boolean equals(T) 就是與Object.equals 衝突的 boolean equals(Object)

這裡寫圖片描述`

泛型規範的另一條規則是:
To support translation by erasure, we impose the restriction that a class or type variable may not at the same time be a subtype of two interface types which are different parameterizations of the same interface.例如:

    class Employee implements Comparable<Employee> { . . . }
    class Manager extends Employee implements Comparable<Manager> { . . .} // Error

Manager 物件這裡就是不合法的。原因可能是在橋方法上存在衝突。一個類實現了Comparable介面就會有一下橋方法:

    public int compareTo(Object other) { return compareTo((X) other); }

這裡就會出現兩個橋方法,可能出現衝突?待證!