一、泛型的概念
泛型實現了引數化型別的概念,使程式碼可以用於多種型別
二、泛型的目的
- 希望類和方法能夠具備最廣泛的表達能力
- 用來指定容器要持有什麼型別的物件,而且由編譯器來保證型別的正確性
三、泛型的使用
-
普通泛型類
public class NormalGenericsClass<T> { private T key; public Generics(T key){ this.key = key; } public T getKey() { return key; } public void setKey(T key) { this.key = key; } } // 泛型類例項 NormalGenericsClass<Integer> a = new NormalGenericsClass<Integer>(0); // Java7 開始支援省略後面的引數型別 NormalGenericsClass<Integer> a = new NormalGenericsClass<>(0);
-
普通泛型介面
// Java中的 Comparator介面 public interface Comparator<T> { int compare(T o1, T o2); boolean equals(Object obj); } // 介面實現例子 java.text.Collator public abstract class Collator implements java.util.Comparator<Object>, Cloneable { //...主體程式碼 }
-
普通泛型方法
private <T> int normalGenericsMethod(Generics<T> i){ // <T> 表示宣告 T為泛型,寫在返回值型別之前 // 也可以宣告多個泛型,如:<K,V> return 0; } // 這是錯誤的,會提示 Cannot resolve symbol `T` /*private int normalGenericsMethod(Generics<T> i){ return 0; }*/
有上界的泛型:
上界:通過關鍵字extends來表示給定的上界,那麼引數就必須是給定的上界或者其子型別。上界可以是某個具體的類或介面,也可以是其他引數 -
上界為具體的類
public class GenericsUpperBound<T extends Number> extends NormalGenericsClass<T> { public GenericsUpperBound(T key){ super(key); } }
-
上界為具體的介面
public <T extends Comparator> T compareWith(T[] arr){ T start = arr[0]; for(int i = 1; i < arr.length; i++){ System.out.print(start.equals(arr[i])); } return start; }
-
上界為其他型別引數
public class OtherUpperBound<T>{ public <T extends E> void otherArgs(NormalGenericsClass<E> a){ // E是OtherUpperBound的型別引數,T是otherArgs方法的型別引數 // T的上界限定為E } } //例子: OtherUpperBound<Number> a = new OtherUpperBound<>; OtherUpperBound<Integer> b = new OtherUpperBound<>; a.otherArgs(b);
四、萬用字元
-
有限定萬用字元
//重寫6中的方法 public void otherArgs(NormalGenericsClass<? extends E> a){ }
取自《Java程式設計的邏輯》8.2
<T extends E> 和 <? extends E>的區別
①<T extends E>:用於定義型別引數,它宣告瞭一個型別引數T,可放在泛型類定義中類名後面、泛型方法返回值前面
②<? extends E>:用於例項化型別引數,它用於例項化泛型變數中的型別引數,只是這個型別是未知的,只知道是E或E的某個子型別 -
無限定萬用字元
public int demo(OtherUpperBound<?> a){ } // 這兩個等效 public <T> int demo(OtherUpperBound<T> a){ }
萬用字元重要限制: 只能讀,不能寫
取自《Java程式設計的邏輯》8.2
總結:
1) 萬用字元形式都可以用型別引數的形式來替代,萬用字元能做的,用型別引數都能做
2) 萬用字元形式可以減少型別引數,形式上往往更為簡單,可讀性也更好,所以,能用萬用字元的就用萬用字元
3) 如果型別引數之間有依賴關係,或者返回值依賴型別引數,或者需要寫操作,則只能用型別引數
4) 萬用字元形式和型別引數往往配合使用,定義必要的型別引數,使用萬用字元表達依賴,並接受更廣泛的資料型別 -
超型別萬用字元(無法用型別引數替代)
public int demo(OtherUpperBound<? super E> a){ }
取自《Java程式設計的邏輯》8.2
總結:
1)萬用字元的目的是為了使方法介面更為靈活,可以接受更為廣泛的型別
2)<? super E>用於靈活寫入或比較,使得物件可以寫入父型別的容器,使得父型別的比較方法可以應用於子類物件,它不能被型別引數形式替代
3)<?>和<? extends E>用於靈活讀取,使得方法可以讀取E或E的任意子型別的容器物件,它們可以用型別引數的形式替代,但萬用字元形式更為簡潔
五、泛型的侷限性
-
父類實現了一個泛型介面,子類希望自定義泛型介面中的方法,只能重寫父類的實現
class Base implements Comparable<Base> class Child extends Base // 希望重寫Comparable的比較方法 /* class Child extends Base implements Comparable<Child> // 錯誤,因為型別擦除的原因,實際實現的都是Comparable介面,介面不允許被實現兩次 */ // 正確重寫Comparable的比較方法的方式 class Child extends Base { @Override public int compareTo(Base o){ if(!(o instanceof Child){ throw new IllegalArgumentException(); } Child c = (Child)o; \實現程式碼 return 0; } \其他程式碼 }
-
型別引數不能作為靜態變數和靜態方法的型別
Class Normal<T>{ public static demo1(T param){ // 錯誤的方法 } public static <E> void demo2(E param){ // 正確的方法 } }
-
不能通過型別引數建立物件
T a = new T(); // error Type parameter `T` cannot be instantiated directly
六、泛型的使用細節
Java中的泛型是通過型別擦除實現的,型別引數在編譯時會被替換為Object
-
對於不同傳入的型別實參,生成的相應物件例項一樣
Generics<Integer> demo = new Generics<>(123); Generics<String> demo2 = new Generics<>("test"); System.out.println(demo.getClass() == demo2.getClass()); // true
-
靜態方法和泛型
靜態方法無法訪問類上定義的泛型;如果靜態方法操作的引用資料型別不確定的時候,必須要將泛型定義在方法上。即:如果靜態方法要使用泛型的話,必須將靜態方法也定義成泛型方法/** 如果在類中定義使用泛型的靜態方法,需要新增額外的泛型宣告(將這個方法定義成泛型方法) 即使靜態方法要使用泛型類中已經宣告過的泛型也不可以。 如:public static void show(T t){..},此時編譯器會提示錯誤資訊 "StaticGenerator cannot be refrenced from static context" */ public static <T> void show(T t){ }
-
泛型的上下邊界新增,必須與泛型的宣告一起
//在泛型方法中新增上下邊界限制的時候,必須在許可權宣告與返回值之間的<T>上新增上下邊界, //即在泛型宣告的時候新增 //public <T> T showKeyName(Generic<T extends Number> container) 編譯器會報錯:"Unexpected bound" public <T extends Number> T showKeyName(Generic<T> container){ System.out.println("container key :" + container.getKey()); T test = container.getKey(); return test; }
- 基本型別不能用於例項化型別引數
泛型要求能包容的是物件型別,基本型別在java中不屬於物件 -
執行時型別查詢只適用於原始型別
NormalGenericsClass<Integer> a = new NormalGenericsClass<>(0); a instanceof NormalGenericsClass<Integer>; // Error Illegal generic type for instanceof a instanceof NormalGenericsClass<T>; // Error Cannot resolve symbol `T` a instanceof NormalGenericsClass; // Pass a instanceof NormalGenericsClass<?>; // Pass a.getClass(); // class com.example.demo.generics.NormalGenericsClass
-
型別推斷只對賦值操作有效
Eg:public class New{ public static <K,V> Map<K,V> map(){ return new HashMap<K,V>(); } } /** -- 方法中傳參 這時編譯器不會執行型別判斷。在這種情況下,編譯器認為:呼叫泛型方法後, 其返回值被賦給一個Object型別的變數 */ public class Test{ public static void main(String args[]){ fun(New.map()); } } public class Test{ public static void main(String args[]){ fun(New.<String, Integer)map()); // 顯示地指明型別 } }
參考資料:
[1]《Java程式設計的邏輯》
[2]《Thinking in Java》
[3] https://blog.csdn.net/s10461/…
[4] https://www.cnblogs.com/lwbqq…