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); }
這裡就會出現兩個橋方法,可能出現衝突?待證!