Java 泛型

前言

Java 泛型(generics)是 JDK 5 中引入的一個新特性, 泛型提供了編譯時類型安全檢測機制,該機制容許開發者在編譯時檢測到非法的類型。java

泛型的本質是參數化類型,也就是說所操做的數據類型被指定爲一個參數。數組

Java中的泛型相似於C ++中的模板。 這個想法容許(Integer, String...等類型以及用戶自定義類型)成爲method, class, interface 的參數。 例如,像HashSet, ArrayList, HashMap等類很好的使用了泛型。安全

爲何須要泛型

讓咱們設想一種場景,建立一個List,用於存儲Integerthis

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對象

經過型咱們能夠作到一下兩點:

  • 使用泛型機制編寫的程序代碼要比那些雜亂的使用Object變量,而後再進行強制類型轉換的代碼具備更好的安全性和可讀性
  • 泛型程序設計(Generic programming) 意味着編寫的代碼能夠被不少不一樣類型的對象所重用

泛型介紹

泛型類

一個泛型類(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);
複製代碼

泛型限定(Bounded Generics)

有時, 類或方法須要對類型變量加以約束。下面是一個典型的例子。 咱們要計算數組中 的最小元素:

可是這裏有個問題? 變量smallest類型爲T,這意味着它能夠是任意類型的對象,怎麼才能肯定T所屬的類有 compareTo方法呢?

解決這個問題的辦法是將T限制爲實現了Comparable接口的類,能夠經過給類型變量T設定限定(bounds)來實現這點:

public static <T extends Comparable> T min(T... a) 複製代碼

如今,泛型的min方法只能被實現了Comparable接口的類(如String, LocalDate等)的數組調用。

多重限定(Multiple Bounds)

一個類型變量或通配符能夠有多個限定, 例如:

<T  extends Comparable &  Serializable>
複製代碼

限定類型用「&」 分隔, 而逗號","用來分隔類型變量(<T, U>)

類型變量能夠有多個限定接口,可是隻能有一個限定類,而且當有限定類存在時,限定類要在限定列表中的第一個

泛型擦除(Type Erasure)

咱們下面看一個例子:

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的泛型機制使用了「擦除」機制。

  • 若是類型變量沒有限定, 擦除後用Object代替T
  • 若是類型變量有限定,查出後使用第一個限定的類型代替T
    若是切換限定: class Interval<T extends Serializable & Comparable>會發生什麼?

若是這樣作, 原始類型用 Serializable 替換 T, 而編譯器在必要時要向 Comparable 插入強制類型轉換。爲了提升效率,應該將標籤(tagging) 接口(即沒有方 法的接口,Serializable接口就是個標籤接口,沒有任何方法)放在邊界列表的末尾

通配符(Wildcards)

通配符用?問號表示,用於指代未知類型。

已知Object是Java中全部類型的超類型,可是List不是任何集合的超類型

例如,List<Object>不是List<String>的超類型,而且將List<Object>類型的變量分配給List<String>類型的變量將致使編譯器錯。 咱們可使用通配符來實現:

// 無界通配符
List<?>
// 上界通配符: 用 extends 關鍵字聲明,表示參數化的類型多是所指定的類型,或者是此類型的子類。
List<? extends E>
// 下屆通配符: 用 super 進行聲明,表示參數化的類型多是所指定的類型,或者是此類型的父類型,直至 Object
List<? super E>
複製代碼

這裏分享兩篇關於通配符的博客:

本文就不過多贅述

相關文章
相關標籤/搜索