【java基礎(五十四)】泛型的約束與侷限(一)

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

不能用基本類型實例化類型參數

不能用類型參數代替基本類型。因此,沒有Pair<double>,只有Pair<Double>。當然,其原因是類型擦除。擦除之後,Pair類含有Object類型的域,而Object不能存儲double值。

這的確令人煩惱。但是,這樣做與Java語言中基本類型的獨立狀態想一致。這並不是一個致命的缺陷,只有8種基本類型,當包裝器類型不能接受替換時,可以使用獨立的類和方法處理它們。

運行時類型查詢只適用於原始類型

虛擬機中的對象總有一個特定的非泛型類型。因此,所有的類型查詢只產生原始類型。如:

if (a instanceof Pair<String>)	// 錯誤的

實際上僅僅測試a是否是任意類型的一個Pair。下面的測試同樣如此:

if (a instanceof Pair<T>) // 錯誤的

或強制類型轉換:

Pair<String> p = (Pair<String>)a;	// 警告

爲提醒這一風險,試圖查詢一個對象是否屬於某個泛型類型時,倘若使用instanceof會得到一個編譯器錯誤,如果使用強制類型轉換會得到一個警告。

同樣的道理,getClass方法總是返回原始類型:

Pair<String> stringPair = ...;
Pair<Employee> employeePair = ...;
if (stringPair.getClass() == employeePair.getClass()) ...;// true

其比較的結果是true,這是因爲兩次調用getClass都將返回Pair.class

不能創建參數化類型的數組

不能實例化參數化類型的數組,如:

Pair<String>[] table = new Pair<String>[10]; // Error

這有什麼問題呢?擦除之後,table的類型是Pair[]。可以把它轉換爲Object[]

Object[] objArray = table;

數組會記住它的元素類型,如果試圖存儲其他類型的元素,就會拋出一個ArrayStoreException異常:

objarray[0] = "Hello"; // Error, component type is Pair

不過對於泛型類型,擦除會使這種機制無效:

objarray[0] = new Pair<Employee>();

能夠通過數組存儲檢查,不過仍會導致一個類型錯誤。處於這個原因,不允許創建參數化類型的數組。

需要說明的是,只是不允許創建這些數組,而聲明類型爲Pair<String>[]的變量仍是合法的。不過不能用new Pair<String>[10]初始化這個變量。

Varargs警告

上面看到Java不支持泛型類型的數組。如果是想參數個數可變的方法傳遞一個泛型類型呢?

考慮下面這個簡單的方法,它的參數個數是可變的:

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

應該記得,實際上參數ts是一個數組,包含提供的所有實參。

現在考慮一下調用:

Collection<Pair<String>> table = ...;
Pair<String> pair1 = ...;
Pair<String> pair2 = ...;
addAll(table, pair1, pair2);

爲了調用這個方法,Java虛擬機必須建立一個Pair<String>數組,這就違反了前面的規則。不過,對於這種情況,規則有所放鬆,你只會得到一個警告,而不是錯誤。

可以採用兩種方法來抑制這個警告。一種方法是爲包含addAll調用的方法增加註釋@SuppressWarnings("unckecked")。或者在Java SE 7中,還可以用@SafeVarargs直接標註addAll方法:

@SafeVarargs
public static <T> void addAll(Collection<T> coll, T... ts)

現在就可以提供泛型類型來調用這個方法了。對於只需要讀取參數數組元素的所有方法,都可以使用這個註解,這僅限於最常見的用例。

不能實例化類型變量

不能使用像new T(...)new T[...]T.class這樣的表達式中的類型變量。如,下面的Pair<T>構造器就是非法的:

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

類型擦除將T改變成Object,而且,本意肯定不希望調用new Object()。在Java SE 8之後,最好的解決方法是讓調用者提供一個構造器表達式。如:

Pair<String> p = Pair.makePair(String::new);

makePair方法接收一個Supplier<T>,這是一個函數式接口,表示一個無參數而且返回類型爲T的函數:

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

比較傳統的解決方法是通過反射調用Class.newInstance方法來構造泛型對象。

遺憾的是,細節有點複雜。不能調用

first = T.class.newInstance(); // Error

表達式T.class是不合法的,因爲它會擦除爲Object.class。必須像下面這樣設計API以便得到一個Class對象:

public static <T> Pair<T> makePair(Class<T> cl) {
	try {
		return new Pair<>(cl.newInstance(), cl.newInstance());
	} catch (Exception ex) {
		return null;
	}
}

這個方法可以按照下列方式調用:

Pair<String> p = Pair.makePair(String.class);

注意,Class類本身是泛型。例如,String.class是一個Class<String>的實例。因此,makePair方法能夠推斷出pair的類型。

捐贈

若你感覺讀到這篇文章對你有啓發,能引起你的思考。請不要吝嗇你的錢包,你的任何打賞或者捐贈都是對我莫大的鼓勵。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章