Java 泛型(generics
)是 JDK 5 中引入的一個新特性, 泛型提供了編譯時類型安全檢測機制,該機制容許開發者在編譯時檢測到非法的類型。java
泛型的本質是參數化類型
,也就是說所操做的數據類型被指定爲一個參數。數組
Java中的泛型相似於C ++中的模板。 這個想法容許(Integer
, String
...等類型以及用戶自定義類型)成爲method
, class
, interface
的參數。 例如,像HashSet
, ArrayList
, HashMap
等類很好的使用了泛型。安全
讓咱們設想一種場景,建立一個List
,用於存儲Integer
this
List list = new LinkedList<>();
list.add(new Integer(1));
Integer i = list.iterator().next();
複製代碼
但編譯器會在最後一行報錯:spa
Object
類型的對象,而咱們須要的是一個Integer對象,所以須要
顯式的強制類型轉換
Integer i = (Integer) list.iterator().next();
複製代碼
這種類型轉換很煩人,由於咱們明明知道list裏面存的是一個Integer類型的對象,卻還要去強轉, 並且類型轉換一旦出現錯誤,在編譯期是沒法發現的,但會在運行期出現java.lang.ClassCastException
異常:設計
若是咱們能夠明確表達咱們想要使用的類型,而編譯器能夠確保這種類型的正確性,則會容易不少。 這正是Java泛型背後的核心思想code
讓咱們將第一行代碼修改一下:cdn
List<Integer> list = new LinkedList<>();
複製代碼
經過<Integer>
咱們將list的範圍縮小到Integer
類型,也就是說咱們指定了list中的類型只能爲Integer
對象
一個泛型類(generic class
)就是具備一個或多個類型變量
的類blog
public class Pairs<T> {
private T first;
private T second;
public Paris() {}
public Pairs(T first, T second) { this.first = first; this.second = second; }
public T getFirst() { return first; }
public void setFirst(T first) { this.first = first; }
public T getSecond() { return second; }
public void setSecond(T second) { this.second = second; }
}
複製代碼
Pair
類引入了一個類型變量T
, 用尖括號(<>
)括起來,並放在類名的後面,泛型類能夠有多個類型變量,例如,能夠定義Paris
類中第一個成員變量和第二個成員變量使用不一樣的類型參數
public class Paris<T, U> {
private T first;
private U second;
}
複製代碼
用具體的類型替換泛型類中的類型變量,就能夠實例化泛型類型, 例如:
Paris<String> paris = new Paris<String>()
Paris<Integer> paris = new Paris<Integer>()
Paris<User> paris = new Paris<User>()
// ...傳入你想使用的類
複製代碼
換句話說, 泛型類可看做普通類的工廠。
上面已經介紹瞭如何定義一個泛型類.實際上還能夠定義一個帶有類型變量的簡單方法
public class Pairs {
public static <T> T getMiddle(T... a) {
return a[a.length/2];
}
}
複製代碼
這個方法是在普通類中定義的, 而不是在泛型類中定義的。 然而, 這是一個泛型方法, 能夠從尖括號(<>
)和類型變量看出這一點。 注意, 類型變量T
放在修飾符 (這裏是 public static
) 的 後面, 返回類型的前面
當調用一個泛型方法時,在方法名前的尖括號中放入具體的類型:
在這種狀況 (實際也是大多數狀況)下, 方法調用中能夠省略 <Integer>
類型參數。 編譯 器有足夠的信息可以推斷出所調用的方法。也就是說,能夠調用:
Pairs.getMiddle(1,2,3,4,5);
複製代碼
有時, 類或方法須要對類型變量加以約束。下面是一個典型的例子。 咱們要計算數組中 的最小元素:
compareTo
方法呢?
解決這個問題的辦法是將T限制爲實現了Comparable
接口的類,能夠經過給類型變量T
設定限定(bounds
)來實現這點:
public static <T extends Comparable> T min(T... a) 複製代碼
如今,泛型的min方法只能被實現了Comparable
接口的類(如String, LocalDate等)的數組調用。
一個類型變量或通配符能夠有多個限定, 例如:
<T extends Comparable & Serializable>
複製代碼
限定類型用「&
」 分隔, 而逗號",
"用來分隔類型變量(<T, U>
)
類型變量能夠有多個限定接口,可是隻能有一個限定類,而且當有限定類存在時,限定類要在限定列表中的第一個
咱們下面看一個例子:
Class<?> class1=new ArrayList<String>().getClass();
Class<?> class2=new ArrayList<Integer>().getClass();
System.out.println(class1); //class java.util.ArrayList
System.out.println(class2); //class java.util.ArrayList
System.out.println(class1.equals(class2)); //true
複製代碼
咱們看輸出發現,class1和class2竟然是同一個類型ArrayList,在運行時咱們傳入的類型變量String和Integer都被丟掉了。Java語言泛型在設計的時候爲了兼容原來的舊代碼,Java的泛型機制使用了「擦除」機制。
若是這樣作, 原始類型用 Serializable 替換 T, 而編譯器在必要時要向 Comparable 插入強制類型轉換
。爲了提升效率,應該將標籤(tagging) 接口
(即沒有方 法的接口,Serializable接口就是個標籤接口,沒有任何方法)放在邊界列表
的末尾
通配符用?
問號表示,用於指代未知類型。
已知Object是Java中全部類型的超類型,可是List不是任何集合的超類型
例如,List<Object>
不是List<String>
的超類型,而且將List<Object>
類型的變量分配給List<String>
類型的變量將致使編譯器錯。 咱們可使用通配符來實現:
// 無界通配符
List<?>
// 上界通配符: 用 extends 關鍵字聲明,表示參數化的類型多是所指定的類型,或者是此類型的子類。
List<? extends E>
// 下屆通配符: 用 super 進行聲明,表示參數化的類型多是所指定的類型,或者是此類型的父類型,直至 Object
List<? super E>
複製代碼
這裏分享兩篇關於通配符的博客:
本文就不過多贅述