【Java】泛型學習筆記

個人博客即將入駐「雲棲社區」,誠邀技術同仁一同入駐。java

參考書籍node

《Java核心技術:卷1》數組

泛型, 先睹爲快

先經過一個簡單的例子說明下Java中泛型的用法:安全

泛型的基本形式相似於模板, 經過一個類型參數T, 你能夠"私人定製"一個類,具體定製的範圍包括實例變量的類型返回值的類型傳入參數的類型函數

Foo.java測試

public class Foo <T> {
  // 約定實例變量的類型
  private T data;
  // 約定返回值的類型
  public T getData () {
    return this.data;
  }
  // 約定傳入參數的類型
  public void setData (T data) {
    this.data = data;
  }
}

 

Test.javathis

public class Test {
  public static void main (String args[]) {
    Foo<String> s = new Foo<String> ();
  }
}

 

泛型的由來

泛型設計源於咱們的編寫類時的一個剛需:想讓咱們編寫的處理類可以更加"通用", 而不是隻能處理某些特定的對象或場景。或者說:咱們但願咱們的類能實現儘量多的複用。舉個栗子:通常來講,你並不想要編寫多個分別處理不一樣數據類型,但內在邏輯代碼卻徹底同樣的類。由於這些處理類可能除了數據類型變換了一下外,全部代碼都徹底一致。「只要寫一個模板類就OK了嘛~ 等要使用的時候再傳入具體的類型,多省心」, 當你這麼思考的時候:浮如今你腦海裏的,就是泛型程序設計(Generic pogramming)的思想編碼

在介紹Java的泛型機制以前, 先讓咱們來看看, 還沒加入泛型機制的「泛型程序設計」是怎樣子的spa

泛型程序設計1.0: 不用Java泛型機制

下面咱們編寫一個存儲不一樣的對象的列表類,列表有設置(set)和取值(get)兩種操做。
假設這個列表類爲ObjArray,同時嘗試存儲的值爲String類型,則:
1.在ObjArray類裏咱們維護一個數組arr, 爲了未來能容納不一樣的對象, 將對象設爲Object類型(全部對象的父類)
2.在實例化ObjArray後, 經過調用set方法將String存入Object類型的數組中; 而在調用get方法時, 要對取得的值作強制類型轉換—從Object類型轉爲String類型設計


ObjArray.java:

public class ObjArray  {
  private Object [] arr;
  public ObjArray(int n) {
    this.arr = new Object[n];
  }
  public void set (int i, Object o) {
    this.arr[i] = o;
  }
  public Object get (int i) {
    return this.arr[i];
  }
}

 


Test.java:

/**
 * @description: 測試代碼
 */
public class Test {
  public static void main (String args[]) {
    ObjArray arr = new ObjArray(3);
    arr.set(0, "彭湖灣");
    // get操做時要作強制類型轉換
    String n =(String)arr.get(0);
    // 輸出 "彭湖灣"
    System.out.print(n);
  }
}

 

 

 

若是不使用泛型機制,但又想要實現泛型程序設計,就會編寫出相似這樣的代碼。

泛型程序設計2.0: 使用Java泛型機制

讓咱們來看看使用泛型機制改進後的結果。
看起來大約是這樣:


GenericArray.java

public class GenericArray<T>  {
  public void set (int i, T o) {
    // ...
  }
  public T get (int i) {
    // ...
  }
}

 【具體代碼下面給出】

 

Test.java:

public class Test {
  public static void main (String args[]) {
    GenericArray<String> arr = new <String>GenericArray(3);
    arr.set(0, "彭湖灣");
    // 不用作強制類型轉換啦~~
    String s =arr.get(0);
    // 輸出: 彭湖灣
    System.out.print(s);
  }
}

 

咱們發現,改進後的設計有如下幾點好處:

1. 規範、簡化了編碼: 咱們不用在每次get操做時候都要作強制類型轉換了
2. 良好的可讀性:GenericArray<String> arr這一聲明能清晰地看出GenericArray中存儲的數據類型
3. 安全性:使用了泛型機制後,編譯器能在set操做中檢測傳入的參數是否爲T類型, 同時檢測get操做中返回值是否爲T類型,若是不經過則編譯報錯

泛型並不是無所不能

瞭解到了泛型的這些特性後, 也許你會火燒眉毛地想要在ObjArray類裏大幹一場。
例如像下面這樣, 用類型參數T去直接實例化一個對象, 或者是實例化一個泛型數組

惋惜的是 ......

public class GenericArray<T>  {
  private T obj = new T (); // 編譯報錯
  private T [] arr = new T[3]; // 編譯報錯
  // ...
}

 

沒錯, 泛型並非無所不能的, 相反, 它的做用機制受到種種條框的限制。

這裏先列舉泛型機制的兩個限制:

1.不能實例化類型變量, 如T obj = new T ();

2. 不能實例化泛型數組,如T [] arr = new T[3];

【注意】這裏不合法僅僅指實例化操做(new), 聲明是容許的, 例如T [] arr

 

咱們如今來繼續看看上面泛型設計中, GenericArray類的那部分代碼:

public class GenericArray<T>  {
  private Object [] arr;
  public GenericArray(int n) {
    this.arr = new Object[n];
  }
  public void set (int i, T o) {
    this.arr[i] = o;
  }
  public T get (int i) {
    return (T)this.arr[i];
  }
}

 

 

沒錯, 在ObjArray類內部咱們仍然仍是用到了強制轉型。看到這裏也許使人有那麼一點點的小失望, 畢竟仍是沒有徹底跳出
初始的泛型設計的邊界。 可是, 泛型的優勢仍然是顯而易見的, 只不過要知道的是:它並無無所不能的魔法, 並受到諸多限制。

 

泛型的編寫規則

1.泛型類和泛型方法的定義

泛型類
如前面所說,能夠像下面同樣定義一個泛型類
類型變量T放在類名的後面

public class Foo <T> {
  // 約定實例變量的類型
  private T data;
  // 約定返回值的類型
  public T getData () {
    return this.data;
  }
  // 約定傳入參數的類型
  public void setData (T data) {
    this.data = data;
  }
}

 

泛型方法
也能夠定義一個泛型方法:

泛型變量T放在修飾符(這裏是public static)的後面, 返回類型的前面

public class Foo {
  public static <T> T getSelf (T a) {
    return a;
  }
}

 

泛型方法能夠定義在泛型類當中,也能夠定義在一個普通類當中

2.可使用多個類型變量

public class Foo<T, U> {
  private T a;
  private U b;
}

 

【注意】在Java庫中,常使用E表示集合的元素類型, K和V分別表示關鍵字和值的類型, T(U,S)表示任意類型

3.JavaSE7之後,在實例化一個泛型類對象時,構造函數中能夠省略泛型類型

ObjArray<Node> arr = new <Node>ObjArray();


可簡寫成:

ObjArray<Node> arr = new <>ObjArray();

 

類型變量的限定

當咱們實例化泛型類的時候, 咱們通常會傳入一個可靠的類型值給類型變量T。 但有的時候,被定義的泛型類做爲接收方,也須要對傳入的類型變量T的值作一些限定和約束,例如要求它必須是某個超類的子類,或者必須實現了某個接口, 這個時候咱們就要使用extends關鍵字了。如:

超類SuperClass:

public class SuperClass {
}


子類SubClass:

public class SubClass extends SuperClass {
}

 

對T使用超類類型限定:要求父類必須爲SuperClass

public class Foo<T extends SuperClass> {
}


測試:

public class Test {
  public static void main (String args[]) {
    Foo<SubClass> f = new Foo<SubClass>(); // 經過
    Foo<String> n = new Foo<String>(); // 報錯
  }
}

 

extends使用的具體規則

1. 對於要求實現接口, 或者繼承自某個父類, 統一使用extends關鍵字 (沒有使用implements關鍵字,爲了追求簡單)

2. 限定類型之間用 "&" 分隔

3. 若是限定類型既有超類也有接口,則:超類限定名必須放在前面,且至多隻能有一個(接口能夠有多個)


這個書寫規範和類的繼承和接口的實現所遵循的規則是一致的(<1>不容許類多繼承,但容許接口多繼承<2>書寫類的時候類的繼承是寫在接口實現前面的)

// 傳入的T必須是SuperClass的子類,且實現了Comparable接口
public class Foo<T extends SuperClass&Comparable> {
}

【注意】: 上面的SuperClass和Comparable不能顛倒順序

 

泛型類的繼承關係

泛型類型的引入引起了一些關於泛型對象繼承關係的有趣(?)問題。

在Java中, 若是兩個類是父類和子類的關係,那麼子類的實例也都是父類的實例,這意味着:
一個子類的實例能夠賦給一個超類的變量:

SubClass sub = new SubClass();
SuperClass sup = sub;

 

當引入了泛型之後, 有趣(?)的問題來了:

 

咱們經過兩對父子類List/ArrayList, Employee/Manager來講明這個問題

(咱們已經知道List是ArrayList的父類(抽象類),這裏假設Employee是Manager的父類)
1. ArrayList<Employee> 和 ArrayList<Manager>之間有繼承關係嗎?(ArrayList<Manager>的實例可否賦給ArrayList<Employee>變量?)
2. List<Employee> 和 ArrayList<Employee>之間有繼承關係嗎?(ArrayList<Employee>的實例可否賦給 List<Employee>變量?)
3. ArrayList和ArrayList<Employee>之間有繼承關係嗎?(ArrayList<Employee>的實例可否賦給ArrayList變量?)

答案以下:

對1: 沒有繼承關係; 不能

ArrayList<Manager> ae = new ArrayList<Manager>();
ArrayList<Employee> am = ae; // 報錯


對2: 有繼承關係; 能夠

ArrayList<Employee> al = new ArrayList<>();
List<Employee> l = al; // 經過


對3: 有繼承關係; 能夠

ArrayList<Employee> ae = new ArrayList<>();
ArrayList a = ae;

 

下面用三幅圖描述上述關係:

 


描述下1,2的關係

對上面三點作個總結:
1. 類名相同,但類型變量T不一樣的兩個泛型類沒有什麼聯繫,固然也沒有繼承關係(ArrayList<Manager>和ArrayList<Employee>)
2. 類型變量T相同,同時原本就是父子關係的兩個類, 做爲泛型類依然保持繼承關係 (ArrayList<Employee>和List<Employee>)
3. 某個類的原始類型,和其對應的泛型類能夠看做有「繼承關係」(ArrayList和ArrayList<Employee>)

引用一幅不太清晰的圖

 

通配符?

泛型繼承關係帶來的問題 — 類型匹配的苦惱

問題出如今上面所述規範中的第二點:ArrayList<Manager> 和 ArrayList<Employee>之間沒有繼承關係。
這意味着,若是你像下面同樣編寫一個處理ArrayList<Employee>的方法

public class Foo {
  public static void handleArr (ArrayList<Employee> ae) {
    // ...
  }
}


你將沒法用它來處理ArrayList<Manager>:

public static void main (String args[]) {
    ArrayList<Manager> am = new ArrayList<Manager>();
    Foo.handleArr(am); // 報錯,類型不匹配
}

 

 

 如今咱們想要:「handleArr方法不只僅能處理ArrayList<Employee>, 並且還能處理ArrayList<X> (這裏X表明Employee和它子類的集合)」。因而這時候通配符?就出現了:ArrayList<? extends Employee>可以匹配ArrayList<Manager>, 由於ArrayList<? extends Employee>是 ArrayList的父類

如今咱們的例子變成了:

public class Foo {
  public static void handleArr (ArrayList<? extends Employee> ae) {
    // ...
  }
}
public static void main (String args[]) {
    ArrayList<Manager> am = new ArrayList<Manager>();
    Foo.handleArr(am); // 能夠運行啦!
  }

 

通配符和super關鍵字

?統配不只能夠用於匹配子類型, 還能用於匹配父類型:

<? super Manager>

 

泛型的其餘約束

上面咱們介紹了泛型的一些約束,例如不能直接實例化實例化類型變量和泛型數組,這裏和其餘約束一塊兒作個總結:

在定義泛型類時不能作的事:


1. 不能實例化類型變量

T obj = new T (); // 報錯, 提示: Type parameter 'T' cannot be instantiated directly

 

解決方案:
若是實在要建立一個泛型對象的話, 可使用反射:

public class GenericObj<T> {
  private T obj;
  public GenericObj(Class<T> c){
      try {
        obj = c.newInstance(); // 利用反射建立實例
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
}

 

/**
 * @description: 測試代碼
 */
public class Test {
  public static void main (String args[]) {
    // 經過
    GenericObj<String> go = new GenericObj<> (String.class);
  }
}


由於Class類自己就是泛型, 而String.class是Class<T>的實例,


2. 不能實例化泛型數組,如T [] arr = new T[3];

private T [] arr = new T[3]; // 報錯, 提示: Type parameter 'T' cannot be instantiated directly

 

解決方法一:

上文所提到的,建立Object類型的數組,而後獲取時轉型爲T類型:

public class GenericArray<T>  {
  private Object [] arr;
  public GenericArray(int n) {
    this.arr = new Object[n];
  }
  public void set (int i, T o) {
    this.arr[i] = o;
  }
  public T get (int i) {
    return (T)this.arr[i];
  }
}

 

解決方法二: 利用反射
這裏使用反射機制中的Array.newInstance方法建立泛型數組

GenericArray.java

public class GenericArray<T> {
  private T [] arr;
  public GenericArray(Class<T> type, int n) {
    arr = (T[])Array.newInstance(type, n); // 利用反射建立泛型類型的數組
  }
  public void set (int i, T o) {
    this.arr[i] = o;
  }
  public T get (int i) {
    return (T)this.arr[i];
  }
}

 

Test.java

/**
 * @description: 測試代碼
 */
public class Test {
  public static void main (String args[]) {
  GenericArray<String> genericArr = new GenericArray<>(String.class, 5);
  genericArr.set(0, "penghuwan");
  System.out.println(genericArr.get(0)); // 輸出 "penghuwan"
  }
}

 

3. 不能在泛型類的靜態上下文中使用類型變量

public class Foo<T> {
  private static T t;
  public static T get () { // 報錯, 提示: 'Foo.this' can not be referenced from a static context
    return T;
  }
}

 


注意這裏說的是泛型類的狀況下。若是是在一個靜態泛型方法中是可使用類型變量的

public class Foo {
  public static<T> T get (T t) { // 經過
    return t;
  }
}


(這裏的泛型方法處在一個非泛型類中)

4. 不能拋出或者捕獲泛型類的實例

不能拋出或者捕獲泛型類的實例:

// 報錯 提示:Generic class may not extend java.lang.throwable
public class Problem<T> extends Exception {
}

 

甚至擴展Throwable也是不合法的

public class Foo {
  public static  <T extends Throwable> void doWork () {
    try {
      // 報錯 提示: Cannot catch type parameters
    }catch (T t) {
    }
  }
}

 

但在異常規範中使用泛型變量是容許的

// 能經過
public class Foo {
  public static  <T extends Throwable> void doWork  (T t) throws T {
    try {
      // ...
    }catch (Throwable realCause) {
      throw  t;
    }
  }
}

 

在使用泛型類時不能作的事

1. 不能使用基本類型的值做爲類型變量的值

Foo<int> node = new Foo<int> (); // 非法

 

應該選用這些基本類型對應的包裝類型

Foo<Integer> node = new Foo<Integer> ();

 


2. 不能建立泛型類的數組

public static void main (String args[]) {
    Foo<Node> [] f =new Foo<Node> [6]; // 報錯
}

 

解決方法:
能夠聲明通配類型的數組, 而後作強制轉換

Foo<Node> [] f =(Foo<Node> [])new Foo<?> [6]; // 經過

 

相關文章
相關標籤/搜索