目錄
- 引言
- 1.為什麼使用泛型?
- 2.定義簡單泛型類
- 3.泛型方法
- 4.類型變量的限定
- 5.限制與局限性
- 6.泛型類型的繼承規則
- 7.通配符
引言
泛型類似于一個模闆,實質就是參數化類型,通過一個類型參數T,用來訓示元素的類型,可以被用在類、接口和方法中,對應的稱為泛型類、泛型接口、泛型方法。
1.為什麼使用泛型?
Java還沒加入泛型類前,泛型程式設計實際是通過繼承實作的。AllayList隻維護一個Object引用的數組
public class ArrayList {
private Object [] arr;
public ObjArray(int n) {
this.arr = new Object[n];
}
public void add (int i, Object o) {
this.arr[i] = o;
}
public Object get (int i) {
return this.arr[i];
}
}
這樣設計有兩個問題:
當擷取一個值時,必須進行強制類型轉換;
可以向數組清單中加入任何類型的對象,但是編譯和運作不報錯;
為此引入泛型機制,在調用get時,不需要進行強制類型轉化,編譯器知道傳回值類型,并且可以避免插入錯誤類型,類型參數T使得程式具有更好的安全性和可讀性。
2.定義簡單泛型類
一個泛型類可以有一個或者多個類型變量的類
public class Pair<T>{...}
public class Pair<T,U>{...}
類型變量使用大寫,Java中,一般使用變量E代表集合的元素類型,K表示關鍵字,V表示關鍵字值的類型,T表示任意類型。
用具體的類型替代類型變量就可以執行個體化泛型類型:Pair<String>(由于泛型的尖括号内容不被顯示,用< 替換<)
3.泛型方法
類型變量放在修飾符後,傳回類型前面;
class ArrayAlg{
public static <T> T getMiddle(T a){
return a[a.length / 2];
}
}
泛型方法可以定義在普通類或者泛型類中;
在執行個體化一個泛型類對象時,構造函數可以省去泛型類型;
4.類型變量的限定
有時,類或方法需要對變量類型進行限制,例如要求它必須是某個超類的子類,或者必須實作了某個接口,那麼被定義的泛型類作為接收方,也需要對傳入的類型變量T的值做一些限定和限制。
public class Pair<T entends SuperClass>{...}
public static <T entends Comparable> T min(T[] a){...}
T和綁定類型可以是類或接口,關鍵字extends相比較implemets更接近子類的概念;
限定類型用&分隔,逗号分隔類型變量;
限定中至多一個類,如果用該類作為限定,必須位于限定清單的第一個,可以擁有多個接口超類型;
5.限制與局限性
先來講一下什麼叫擦除?無論何時定義一個泛型類型,都自動提供一個相應的原始類型。原始類型的名字就是删去類型參數後的泛型類型名。擦除類型變量,并替換為限定類型(無限定的變量用object)
1.不能使用基本類型執行個體化類型參數
不能使類型參數替代基本類型,應使用基本類型對應的包裝器類型,當包裝器類型不能接受替換時,可以用獨立的類或者方法處理。
Pair<int> node = new Pair<int> (); // 非法
Pair<Integer> node = new Pair<Integer> ();//合法
2.運作時類型查詢隻适用于原始類型
所有類型查詢隻産生原始類型,無論何時使用instanceof或者泛型類型的強制類型轉換表達式都會報編譯器錯誤。
if(a instanceof Pair<T>) //error
Pair<String> p=(Pair<String>) a;//warning--can only test that a is a Pair
當類型變量不同時,假如使用getClass方法比較,可能會得到true,兩次調用getClass都将傳回Pair.class
3.不能建立參數化類型的數組
不能執行個體化參數化類型的數組,如下代碼會報錯
可以把它轉換為Object[]類型的數組,數組會記得它的元素類型,Object[] obj=table;
需要注意的是,隻是不允許建立這些數組,但聲明類型為Pair<String>[]的變量是合法的,不可以用new Pair<String>[10]初始化變量。
另外,如果需要收集參數化類型對象,可以使用AllayList:ArrayList<Pair<String>>
4.不能執行個體化類型變量
不能使用new T(…) , new T[…] , T.class這樣的表達式中的類型變量,可以通過反射Class.newInstance方法來構造泛型對象,如下代碼來支配class對象:
public static <T> Pair<T> makePair(Class<T> cl){
try{
return new Pair<> (cl.newInstance(),cl.newInstance())}
catch(Exception ex){return null;}
}
可以使用如下調用:String.class實際上是Class<String>的唯一一個執行個體,方法能推斷出Pair的類型
5.泛型類的靜态上下文中類型變量無效
public class Pair<T> {
private static T t;
public static T get () {
/* 報錯
提示: 'Pair.this' can not be referenced from a static context
由于類型擦除後,隻有Pair類,包含一個t域,不能在靜态域或方法中引用類型變量
*/
return T;
}
}
6.不能抛出或者捕獲泛型類的執行個體
不能抛出或者捕獲泛型類對象,包括泛型類拓展Throwable也不合法,如下:
public class Pair {
public static <T extends Throwable> void doWork () {
try {
do work
}catch (T t) {// 報錯 提示: Cannot catch type parameters
}
}
}
java異常進行中使用泛型可以消除對已檢查異常的檢查,如下是合法的:
public class Pair {
public static <T extends Throwable> void doWork (T t) throws T {
try {
do work
}catch (Throwable realCause) {
throw t;
}
}
}
6.泛型類型的繼承規則
必須注意泛型與Java數組之間的差別,還是用雇員及經理來說,我們可以将一個Manager[]數組指派給一個類型為Employee[]的變量,但是絕對不可以将Pair<Manager>轉換為Pair<Employee>,另外泛型類可以拓展或者實作其他的泛型類,例如ArrayList<T類可以實作List<T>, 用一張圖來形象的說明以上情況:
7.通配符
固定的泛型類型可能使用中并不是很愉快,假設我們要編寫一個列印雇員的方法,但是向之前所說,不能将Pair<Manager> 傳遞進來,是以Java的設計者提出了“通配符類型”來解決它。
1 .有限定通配符
public static void printBuddis(Pair<? entends Employee> p){
Employee first = p.getFirst();
...
}
有限定通配符存在一個問題,由于編譯器隻知道某個Employee的子類型,但是并不知道的具體什麼類型,是以不能調用setFirst()方法
2.超類型限定通配符
帶有超類型限定的通配符可以為方法提供參數,但是不能使用傳回值;也就是說帶有超類型限定的通配符可以向泛型對象寫入,帶有子類型限定的通配符可以從泛型對象讀取;
?super Manager該通配符限制為Manager的所有超類型
3.無限定通配符
Pair<?>和Pair的本質差別在:可以為任意Object對象調用原始的Pair類的setObject調用,有方法如下:
? getFirst()
void setFirst(?)//可以調用setFirst(null)
可以測試pair是否包括一個null引用
public static boolean hasNull(Pair<?> p){
return p.getFirst()==null|| p.getSecond()==null;
}
4.通配符擷取
編寫一個交換一個pair元素的方法 :
public static void swap(Pair<?>)
通配符不是類型變量,是以不能再代碼中使用 ?為一種類型。代碼 ?t=p.getFrist(); 是非法的。我們可以編寫一個輔助方法swapHelper,如下:
public static <T> void swapHelper(Pair<T> p){
T t=p.getFrist();
p.setFirst(p.getSecond());
p.setSecond(t);
}
現在我們可以在swap方法中調用swapHelper了
參考書籍:《Java核心卷Ⅰ》