個人博客即將入駐「雲棲社區」,誠邀技術同仁一同入駐。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
下面咱們編寫一個存儲不一樣的對象的列表類,列表有設置(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); } }
若是不使用泛型機制,但又想要實現泛型程序設計,就會編寫出相似這樣的代碼。
讓咱們來看看使用泛型機制改進後的結果。
看起來大約是這樣:
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類內部咱們仍然仍是用到了強制轉型。看到這裏也許使人有那麼一點點的小失望, 畢竟仍是沒有徹底跳出
初始的泛型設計的邊界。 可是, 泛型的優勢仍然是顯而易見的, 只不過要知道的是:它並無無所不能的魔法, 並受到諸多限制。
泛型類
如前面所說,能夠像下面同樣定義一個泛型類
類型變量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; } }
泛型方法能夠定義在泛型類當中,也能夠定義在一個普通類當中
public class Foo<T, U> { private T a; private U b; }
【注意】在Java庫中,常使用E表示集合的元素類型, K和V分別表示關鍵字和值的類型, T(U,S)表示任意類型
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>(); // 報錯 } }
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 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]; // 經過