本文是本人學習《JavaSE學習筆記 - 泛型基礎》這篇文章的筆記,記錄自己學習理解的過程,主要是作爲備忘。建議大家直接看《JavaSE學習筆記 - 泛型基礎》這篇文章原文後再來看本文。
泛型基礎知識
- Java SE5加入的新特性
- 實現了參數化類型
- 編譯期類型檢查
我很喜歡參數化類型這種表達,清晰明瞭。可以類比一個普通方法參數的概念。一個方法中的參數用一個形參來表示,到具體使用的時候傳入具體的實參才確定值。在泛型中,在編譯時並未指定具體的類型,用類型形參表示,到具體使用泛型類的時候才指定具體的類型實參。可以把<T>中的T想象成一個方法中的形參,在使用泛型類的時候傳入具體的類型,比如<String>,這個具體的類型String相當於傳入方法中的值
參數化泛型類型可以理解爲執行泛型類型調用,這個過程就像執行函數調用,但有所區別的是執行該調用是傳遞類型作爲實參。
泛型方法
- 定義泛型方法的語法也很簡單,只要將
<T>
這樣的泛型描述置於方法的返回值之前就可以了。 - 定義泛型方法時所指定的泛型形參的可見性只限定在該泛型方法,就如方法中的局部變量一樣,所以,就算泛型類有同名的類型形參,也是與泛型方法中的類型形參沒有任何關係。
-
如果使用泛型方法可以取代將整個類泛型化,那麼就應該只使用泛型方法,因爲這樣可以使得代碼更清晰簡單
靜態方法無法訪問泛型類的類型形參,如果需要將泛型特性應用到靜態方法上,則必須使用泛型方法了
public class GenericClass<T> {
// error: Cannot make a static reference to the non-static type T
//! public static void staticMethod(T t) { }
}
public class GenericClass {
// ok
public static <T> T staticMethod(T item) { return item; }
}
如下代碼,靜態泛型方法與泛型類都有同名的類型形參T
,但編譯器沒有發出任何錯誤或警告。而非靜態泛型方法,編譯器卻發出警告The type parameter T is hiding the type T
,表示非靜態泛型方法的類型形參T把泛型類的類型形參隱藏起來。靜態泛型方法本身無法訪問泛型類類型形參,所以不會觸發隱藏現象。
泛型方法中T和泛型類中的T是毫不相關的類型,建議用不同的名字區分,提高代碼的可讀性
public class GenericClass<T> {
// ok
public static <T> T staticMethod(T item) { return item; }
// warning: The type parameter T is hiding the type T
@SuppressWarnings("hiding")
public <T> T method(T item) { return item; }
}
泛型方法調用時顯式的類型說明
public class ExplicitTypeSpecification {
private <K, V> Map<K, V> map() {
return new HashMap<K, V>();
}
private static <K, V> Map<K, V> static_map() {
return new HashMap<K, V>();
}
static void f(Map<String, List<Integer>> map) { }
void g(Map<String, List<Integer>> map) { }
public void nonStaticMethod() {
// The method g(Map<String,List<Integer>>) in the type ExplicitTypeSpecification
// is not applicable for the arguments (Map<Object,Object>)
//! g(map());
g(this.<String, List<Integer>>map());
}
public static void staticMethod() {
f(ExplicitTypeSpecification.<String, List<Integer>>static_map());
}
}
以上代碼之所以要使用顯示的類型聲明時因爲既沒有傳入的參數類確定類型,也沒有一個聲明的變量來接收map()方法的返回值來確定泛型。其實以上代碼也可以這樣寫,利用返回值接收對象的類型自動類型推斷,就是要多些一行代碼,如下所示:
public void nonStaticMethod() {
Map<String, List<Integer>> map = map();
g(map);
}
public static void staticMethod() {
Map<String, List<Integer>> map = ExplicitTypeSpecification.static_map();
f(map);
}
類型推斷
在調用泛型方法時,我們沒有顯式指明類型實參時,編譯器會根據泛型方法所接收的參數類型或即將返回賦值給的目標對象類型來決定泛型方法類型形參的具體類型到底是什麼
- 根據參數類型推斷
- 根據返回值接收對象類型推斷
- 若同種類型形參接收多個不同類型的泛型實參,會推斷出這些泛型實參的共同父類
static <T> T f(T itemA, T itemB) {
return itemB;
}
static <T> List<T> list() {
return new ArrayList<T>();
}
public static void main(String[] args) {
// 通過方法的參數推斷,由於和1.5分屬Integer和Double類型,那麼會推斷出共同的父類,即Number
Number number = f(1, 1.5);
// 根據返回值接收目標類型推斷
List<Integer> iList = g();
// 與上面的一樣,這種語法只能在Java SE7及以上的版本才能使用
List<Double> dList = new ArrayList<>();
}
顯示類型說明
類型推斷只對賦值操作有效,其他時候並不起作用。如下所示代碼create()方法未賦值,執行結果直接傳給accept方法(),並不能夠根據accept方法的接收類型自動推斷出泛型的類型。
public class LimitsOfInference {
static void accept(Map<String, Integer> map) { }
static <K, V> Map<K, V> create() {
return new HashMap<K, V>();
}
public static void main(String[] args) {
// The method accept(Map<String,Integer>) in the type LimitsOfInference
// is not applicable for the arguments (Map<Object,Object>)
accept(create());
}
}
上面代碼問題可以根據泛型的顯示類型說明來解決
accept(LimitsOfInference .<String,Integer>create());