以下內容是我對 Java 8 編程參考官方教程(第9版) 該書的讀書筆記
一、概述
泛型是在 JDK 1.5 引入的,泛型的意思是參數化類型,通過泛型可以創建以類型安全的方法使用各種類型數據的類、接口以及方法,能夠使一份算法獨立於特定的數據類型,然後將算法應用於各種數據類型而不需要做額外的各種。 Object是所有其他類的超類,Object引用變量可以引用所有類型的對象,因此通過操作Object類型的引用,Java總是可以操作一般化的類、接口以及方法,但它們不能以類型安全的方式進行工作。 泛型提供了以前缺失的類型安全性,並且不再需要顯式地使用強制類型轉換,所有的類型轉換都是自動和隱式進行的。
二、泛型示例
2.1、帶一個參數類型的泛型類
下面看一個簡單的泛型類 User
public class User<T> { private T t; public User(T t) { this.t = t; } public void showType() { System.out.println("T: " + t); System.out.println("Name: " + t.getClass().getName()); } }
其中,T是實際參數類型的佔位符,當創建對象時,就會將實際類型傳給 User。在 showType() 中輸出參數值和參數值的類對象名稱
public class GenericMain { public static void main(String[] args) { User<String> stringUser = new User<>("leavesC"); stringUser.showType(); System.out.println(); User<Integer> integerUser = new User<>(24); integerUser.showType(); } }
運行結果
image
2.2、帶多個參數類型的泛型類
在泛型類中可以聲明多個類型參數,通過逗號分隔參數列表
public class User<T, K> { private T t; private K k; public User(T t, K k) { this.t = t; this.k = k; } public void showType() { System.out.println("T: " + t); System.out.println("T Name: " + t.getClass().getName()); System.out.println("K: " + k); System.out.println("K Name: " + k.getClass().getName()); } }
public class GenericMain { public static void main(String[] args) { User<String, Integer> stringUser = new User<>("leavesC", 24); stringUser.showType(); System.out.println(); User<String, Double> integerUser = new User<>("葉應是葉", 24.0); integerUser.showType(); } }
運行結果
image
此處指定了兩個類型參數:T 和 K,在創建User對象時就需要爲之傳遞兩個類型參數,但不要求兩個類型參數必須是相同的類型
2.3、泛型接口
除了可以定義泛型類外,還可以定義泛型接口
例如,定義一個泛型接口
public interface Control<T> { T test(); }
在泛型類中實現泛型接口
public class User<T, K> implements Control<T> { private T t; private K k; public User(T t, K k) { this.t = t; this.k = k; } public void showType() { System.out.println("T: " + t); System.out.println("T Name: " + t.getClass().getName()); System.out.println("K: " + k); System.out.println("K Name: " + k.getClass().getName()); } @Override public T test() { return null; } }
在非泛型類中實現泛型接口
public class Stats implements Control<String> { @Override public String test() { return null; } }
三、泛型只能使用引用類型
當聲明泛型類的實例時,傳遞的類型參數必須是引用類型,不能使用基本類型 例如,對於 User 泛型類來說,以下聲明是非法的
User<int,double> user=new User<>(1,10,0);
IDE 會提示類型參數不能是基本數據類型
image
可以使用類型封裝器封裝基本類型來解決該問題
四、有界類型
對於 User 泛型類來說,可以使用任何類代替類型參數,但有時候我們也會有限制能夠傳遞給類型參數的類型的需求。例如,希望創建一個對於所有的數值類型都能夠計算平均值的方法,包括整數、單精度浮點數和雙精度浮點數等。
對於所有的數值類,比如 Integer 以及 Double,都是 Number 類的子類,而 Number 類定義了 doubleValue() 方法,所以所有的數值封裝器都可以使用該方法。但編譯器不知道我們試圖創建的是隻使用數值類型的 Stats 對象,所以 doubleValue() 方法對編譯器來說是未知方法,因此在試圖編譯時以下泛型類會報錯。
public class Stats<T> { private T[] numbers; public Stats(T[] numbers) { this.numbers = numbers; } public double average() { double sum = 0; for (T t : numbers) { //錯誤,因爲並不是每種類型參數都包含 doubleValue 方法 //需要我們主動告訴編譯器我們傳入的類型參數都是Number類的子類 sum += t.doubleValue(); } return sum / numbers.length; } }
爲了處理這種情況,需要爲泛型類提供有界類型。在指定類型參數時,可以創建聲明超類的上界,所有類型參數都必須派生自超類,通過關鍵字 extends 來聲明限制。
這樣,T 只能被 Number 類或其子類替代,阻止創建非數值類型的Stats對象
public class Stats<T extends Number> { private T[] numbers; public Stats(T[] numbers) { this.numbers = numbers; } public double average() { double sum = 0; for (T t : numbers) { sum += t.doubleValue(); } return sum / numbers.length; } }
public class GenericMain { public static void main(String[] args) { Integer[] intNumbers = {1, 2, 3, 4}; Stats<Integer> integerStats = new Stats<>(intNumbers); System.out.println("int numbers average: " + integerStats.average()); System.out.println(); Double[] doubleNumbers = {1.0, 2.0, 3.0, 4.0}; Stats<Double> doubleStats = new Stats<>(doubleNumbers); System.out.println("double numbers average: " + doubleStats.average()); } }
運行結果
image
此外,除了使用類作爲邊界外,邊界也可以包含一個類和一個或多個接口。聲明方式是:先指定類類型,再使用 & 運算符連接接口 例如
class Test<T extends MyClass & MyInterface1 & MyInterface2>
五、使用通配符參數
5.1、一般用法
現在來爲上一節的 Stats 泛型類添加一個比較 Stats 對象之間包含的數組的平均值是否相等的方法。 這看起來可能並不麻煩,似乎以下的代碼就可以滿足需求
public class Stats<T extends Number> { private T[] numbers; public Stats(T[] numbers) { this.numbers = numbers; } public double average() { double sum = 0; for (T t : numbers) { sum += t.doubleValue(); } return sum / numbers.length; } public boolean sameAvg(Stats<T> ob) { return average() == ob.average(); } }
但在使用的過程中,你就會發現 sameAvg 方法給我們帶來了極大的限制,因爲 sameAvg() 方法的參數值泛型類型被指定爲和被調用對象的參數類型是相同的
如下的調用方式會使編譯器提示錯誤。因此,這種方式的適用範圍很窄,無法得到泛型化的解決方案。
image
爲了創建泛型化的 sameAvg() 方法,必須使用泛型的另一個特性:通配符參數。通配符參數由 “?” 指定,表示未知類型。在此,Stats< ? > 和所有的 Stats 對象相匹配,允許任意兩個Stats對象比較它們的平均值。通配符只是簡單地匹配所有有效的Stats對象
public boolean sameAvg(Stats<?> ob) { return average() == ob.average(); }
5.2、有界通配符
如果要爲通配符建立上邊界,可以使用如下所示的通配符表達式:
<? extends superClass>
其中,superClass 是作爲上界的類的名稱。這是一條包含語句,即形成上界的類(即superClass)也包含於邊界之內
此外還可以爲通配符添加一條super子句,爲通配符指定下界
<? super subClass>
其中,只有 subClass 的超類是可接受參數。這是一條排除子句,即 subClass 指定的類不包含在內
六、創建泛型方法
泛型類是在實例化類的時候指明參數的具體類型,而泛型方法是在調用方法的時候指明參數的具體類型 。 創建泛型方法需要依照一定的規則:可以在非泛型類中創建泛型方法,類型參數列表要位於返回類型之前 類似於
public <T> void mothodName(T t) { }
在泛型類中聲明的一般方法與泛型方法是有一些概念與使用區別的
public class Stats<T> { private T[] numbers; public Stats(T[] numbers) { this.numbers = numbers; } //這不是泛型方法,只是使用了 T 作爲泛型參數而已 public double average(T t) { return 1; } //這是一個泛型參數, //在泛型類 Stats<T> 中聲明瞭一個泛型方法,使用泛型 K,泛型 K 可以爲任意類型,既可以與 T 相同,也可以不同 //由於泛型方法聲明瞭泛型參數<K>,因此即使在泛型類 Stats<T> 中並未聲明 K,編譯器也能夠正確識別出泛型方法中使用到的參數類型 public <K> void mothodName(K t) { } }
七、使用泛型的限制
7.1、模糊性錯誤
看如下例子 對泛型類 User< T, K > 而言,聲明瞭兩個泛型類參數:T 和 K。在類中試圖根據類型參數的不同重載 set() 方法。這看起來沒什麼問題,可編譯器會報錯
public class User<T, K> { //重載錯誤 public void set(T t) { } //重載錯誤 public void set(K k) { } }
首先,當聲明 User 對象時,T 和 K 實際上不需要一定是不同的類型,以下的兩種寫法都是正確的
public class GenericMain { public static void main(String[] args) { User<String, Integer> stringIntegerUser = new User<>(); User<String, String> stringStringUser = new User<>(); } }
對於第二種情況,T 和 K 都將被 String 替換,這使得 set() 方法的兩個版本完全相同,所以會導致重載失敗。
此外,對 set() 方法的類型擦除會使兩個版本都變爲如下形式:
public void set(Object o) { }
一樣會導致重載失敗。
7.2、不能實例化類型參數
不能創建類型參數的實例。因爲編譯器不知道創建哪種類型的對象,T 只是一個佔位符
public class User<T> { private T t; public User() { //錯誤 t = new T(); } }
7.3、對靜態成員的限制
靜態成員不能使用在類中聲明的類型參數,但是可以聲明靜態的泛型方法
public class User<T> { //錯誤 private static T t; //錯誤 public static T getT() { return t; } //正確 public static <K> void test(K k) { } }
7.4、對泛型數組的限制
不能實例化元素類型爲類型參數的數組,但是可以將數組指向類型兼容的數組的引用
public class User<T> { private T[] values; public User(T[] values) { //錯誤,不能實例化元素類型爲類型參數的數組 this.values = new T[5]; //正確,可以將values 指向類型兼容的數組的引用 this.values = values; } }
此外,不能創建特定類型的泛型引用數組,但使用通配符的話可以創建指向泛型類型的引用的數組
public class User<T> { private T[] values; public User(T[] values) { this.values = values; } }
public class GenericMain { public static void main(String[] args) { //錯誤,不能創建特定類型的泛型引用數組 User<String>[] stringUsers = new User<>[10]; //正確,使用通配符的話,可以創建指向泛型類型的引用的數組 User<?>[] users = new User<?>[10]; } }
7.5、對泛型異常的限制
泛型類不能擴展 Throwable,意味着不能創建泛型異常類