為什么要使用泛型?
未使用泛型的情況:
// 創建列表類
List list = new ArrayList();
// 添加一個類型為 String 的列表元素
list.add("hello");
// 強制轉換為 String 類型,再賦值給類型為 s 的引用變量
String s = (String) list.get(0);
使用泛型的情況:
// 創建泛型類,<String> 為類型參數
List<String> list = new ArrayList<String>();
// 添加一個類型為 String 的列表元素
list.add("hello");
// 這里不需要強制類型轉換
String s = list.get(0);
好處:實現通用的泛型算法,處理不同類型的集合,可以自定義類型,類型安全,便於閱讀。
泛型類型
一個泛型類型是一個類型參數化(<類型參數>)的泛型類或接口。
一個簡單的 Box 類
public class Box {
private Object object;
public void set(Object object) { this.object = object; }
public Object get() { return object; }
}
Box 類中方法接受或返回一個對象,除了基本類型外你可以傳入任何對象。編譯時無法檢查類是如何使用的,如果傳入一個 Integer 並希望獲取 Integer,但卻錯誤傳入 String 對象,那么就會導致運行錯誤。
泛型版本 Box 類
泛型類型定義格式:
class name<T1, T2, ..., Tn> { /* ... */ } //T1, T2, ..., Tn為類型參數
類型參數(也成為類型變量)跟在類名稱后面,類型參數放在尖括號(<>)中(T1, T2, ..., and Tn),
修改后的泛型 Box 類:
/**
* Box 類的泛型版本
* @param <T> 類型的值被裝箱
*/
public class Box<T> {
// T 表示 "類型"
private T t;
public void set(T t) { this.t = t; }
public T get() { return t; }
}
上面代碼中,Box 類中的所有 Object 類型都被替換為 T 類型,類型變量可以是指定的任何非基本類型:任何類類型、任何接口類型、任何數組類型,甚至是任何其他的類型變量。
泛型技術也可以實現通用的泛型接口。
類型參數的命名約定
按照慣例,類型參數名為單個大寫字母。這與已知的變量命名約定形成鮮明對比,這樣做有充分的理由:如果沒有這個約定,那么很難區分類型變量和普通類或接口名稱之間的區別。
最常用的類型參數名稱:
- E - Element (used extensively by the Java Collections Framework)
- K - Key
- N - Number
- T - Type
- V - Value
- S,U,V etc. - 2nd, 3rd, 4th types
調用和實例化泛型類型
用具體的類型替換類型變量 T 就可以實例化泛型類型,例如:
Box <Integer> integerBox = new Box <Integer>();
通用類型的調用一般稱為“參數化類型”。
像往常一樣使用new
關鍵字實例化,但在類名和括號之間放置<Integer>
:
在 Java SE 7 和更高的版本中,只要編譯器可以根據上下文確定或推斷類型參數,就可以用一組空類型參數(<>)替換調用泛型類的構造函數所需的類型參數。例如:
Box<Integer> integerBox = new Box<>();
多類型參數
泛型類可以擁有多個類型參數。例如,實現通用 Pair 接口的通用 OrderedPair 類:
public interface Pair<K, V> {
public K getKey();
public V getValue();
}
public class OrderedPair<K, V> implements Pair<K, V> {
private K key;
private V value;
public OrderedPair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
}
以下語句創建 OrderedPair 類的兩個實例:
Pair <String,Integer> p1 = new OrderedPair <String,Integer>(“Even”,8);
Pair <String,String> p2 = new OrderedPair <String,String>(“hello”,“world”);
新的OrderedPair <String,Integer>
代碼將 K 實例化為一個字符串,將 V 實例化為一個整數。因此,OrderedPair 的構造函數的參數類型分別是 String 和 Integer。由於自動裝箱,將 String 和 int 傳遞給類是有效的。
正如前文所述,由於Java編譯器可以從OrderedPair <String,Integer>
聲明中推斷 K 和 V 類型,因此可以使用以下縮寫:
OrderedPair <String,Integer> p1 = new OrderedPair <>(“Even”,8);
OrderedPair <String,String> p2 = new OrderedPair <>(“hello”,“world”);
參數化類型
您也可以用參數化類型(即List
OrderedPair <K,V>
示例:
OrderedPair <String,Box <Integer>> p = new OrderedPair <>(“primes”,new Box <Integer>(...));
原始類型
原始類型是沒有任何類型參數的泛型類或接口的名稱。
例如,給定一個 Box 泛型類:
public class Box<T> {
public void set(T t) { /* ... */ }
// ...
}
要創建參數化類型,為形式類型參數 T 提供實際類型參數:
Box<Integer> intBox = new Box<>();
如果省略實際類型參數,則會創建一個Box<T>
的原始類型:
Box rawBox = new Box();
因此,Box 是泛型Box<T>
的原始類型。但是,非泛型類或接口類型不是原始類型。
在 JDK5.0 之前很多 API 類(如 Collections 類)不是通用的,為了向后兼容,允許將參數化類型分配給其原始類型:
Box<String> stringBox = new Box<>();
Box rawBox = stringBox; // OK
但是,將原始類型分配給參數化類型,編譯器會發出一個警告:
Box rawBox = new Box(); // rawBox 是 Box<T> 的原始類型
Box<Integer> intBox = rawBox; // warning: unchecked conversion
如果使用原始類型來調用相應泛型類型中定義的泛型方法,則還會收到警告:
Box<String> stringBox = new Box<>();
Box rawBox = stringBox;
rawBox.set(8); // warning: unchecked invocation to set(T)
該警告顯示原始類型繞過了泛型類型檢查,將不安全代碼的捕獲推遲到運行時。因此,你應該避免使用原始類型。