概述
什麼是泛型?泛型能解決什麼問題?
泛型即引數化型別,會讓你的程式更易讀,也更安全。
在Java增加泛型特性前【JDK5前】,泛型的程式的設計是用繼承來實現的。如下程式可以正常編譯和執行,但將get的結果強制型別轉換會產生一個錯誤。為了解決這個問題,泛型應運而生。
/**
* 不同型別的元素add到ArrayList中
*/
@Test
public void mixedValueTest(){
ArrayList mixedValue = new ArrayList();
mixedValue.add(1L);
mixedValue.add("string");
mixedValue.forEach(item->{
System.out.println((String)item);
});
}
// java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.String
在泛型使用過程中,操作的資料型別被指定為一個引數,這種引數型別可以用在類、介面和方法中,分別被稱為泛型類、泛型介面、泛型方法。
特性
泛型只在編譯階段有效,在編譯時會採取去泛型化的措施:將泛型相關的資訊擦除,同時在物件進入和離開方法的邊界處新增型別檢查和型別轉換的方法。也就是說,泛型資訊不會進入到執行階段。
/**
* 使用型別引數約束元素的型別
*/
@Test
public void traitTest(){
ArrayList<String> stringList = new ArrayList<String>();
stringList.add("string a");
stringList.add("string b");
ArrayList<Integer> IntegerList = new ArrayList<Integer>();
IntegerList.add(100);
IntegerList.add(200);
if(stringList.getClass()==IntegerList.getClass()){
System.out.println("泛型型別在邏輯上可以看成是多個不同的型別,實際上都是相同的型別");
}
}
泛型的定義和使用
泛型有三種使用方式:泛型類、泛型介面、泛型方法。
泛型類
泛型型別用於類的定義中,被稱為泛型類,泛型類例項化時需要傳入泛型型別實參【不傳預設為Object】。最典型的是各種容器類List
、ArrayList
、Set
、Map
。
1. 泛型類的定義
class 類名稱 <泛型標識:可以隨便寫任意標識號,標識指定的泛型的型別>{
private 泛型標識 變數名;
.....
}
}
常用的泛型標識【一般使用大寫字母標識】: K/V/T/E/U/S/R......
2. 泛型類的使用
// 泛型相當於普通類的工廠
類目<具體的資料型別> 物件名 = new 類名<具體的資料型別[JDK7後可省略]>();
3. 從泛型類派生子類
// 子類也是泛型類,子類和父類的泛型型別要一致,只能在父類的基礎上進行擴充套件【子類必須包含父類的泛型標識】
class 子類名<泛型標識:T> extends 父類名<泛型標識:T>
// 子類不是泛型類,父類要明確泛型的資料型別
class 子類名 extends 父類名<明確的資料型別>
注意事項:
- 泛型的型別引數只能是Object型別,不能是基本資料型別
- 如果沒有指定具體的資料型別。預設操作的型別是Object
- 泛型型別在邏輯上可以看成是多個不同的型別,但實際上都是相同的型別
泛型介面
泛型介面與泛型類的定義及使用基本相同。
1. 泛型介面的定義
interface 介面名稱<泛型標識:可以隨便寫任意標識號,標識指定的泛型的型別>{
private 泛型標識 方法名();
.....
}
2. 泛型介面的使用
// 實現類是泛型類,實現類和介面的泛型型別保持一致
class 實現類<T> implements 泛型介面<T>
// 實現類不是泛型類,介面要明確資料型別
class 實現類 implements 泛型介面<String>
泛型方法
泛型方法:是在呼叫方法的時候指明泛型的具體型別,泛型方法中的泛型標識跟泛型類中的泛型標識無關,可以看成一個獨立的體系。
1. 泛型方法定義
// 關鍵在於修飾符後的菱形語法<E>
修飾符 <E> 返回值型別 方法名(E ele){
return Objects.isNull(ele);
}
// 修飾符後的菱形語法<E>,可以理解宣告此方法為泛型方法,泛型類的使用了泛型的成員方法並不是泛型方法。
2. 泛型方法和可變引數
public <E> void printElement(E... elements){
for(E element : elements){
System.out.println(element);
}
}
3. 泛型方法的使用
printElement("string",100,true);
小結:
- 泛型方法能是方法獨立於類而產生變化
- 無論何時,如果能做到,就該儘量使用泛型方法,而不是將類泛型化
- 如果static方法要使用泛型能力,就必須將其定義為泛型方法,原因是靜態方法無法訪問類上定義的泛型。
萬用字元
泛型標識本質上也是萬用字元,沒啥區別,是編碼時約定俗成的東西。
通常情況下,T,E,K,V,? 是這樣約定的:
- ? 表示不確定的 java 型別
- T (type) 表示具體的一個java型別
- K V (key value) 分別代表java鍵值中的Key Value
- E (element) 代表Element
? 和 T 的區別
T
是一個確定的型別,通常用於泛型類和泛型方法的定義,?
是一個不確定的型別,通常用於泛型方法的呼叫程式碼和形參,不能用於定義類和泛型方法。
通過
T
可以確定泛型引數的一致性// ?本身就是不確定的元素,不能保證List的元素是一致的 void t1(List<? extends Number> list); // 確保泛型引數是一致的,List中儲存相同的元素 void t1(List<T> list);
型別引數
T
可以多重限定,而萬用字元?
不行萬用字元可以使用超類限定而型別引數不行
T extends Number // 允許 T susper Number // 不允許 ? extends Number // 允許 ? susper Number // 允許
變數型別限定
上界萬用字元 < ? extends E>
上界用extends
關鍵字宣告,表示引數化的型別可能是所指定的型別及其子類:
// 限定型別實參實現了Comparable介面,其中T表示限定型別的子型別,Comparable為限定型別的上界
public static <T extends Comparable> T min(T... comparbleElements)
// 一個型別變數或萬用字元可以有多個限定
public static <T extends Comparable & Serializable> T min(T... comparableElements)
public static <? extends Comparable & Serializable> T min(T... comparableElements)
小結:
- 如果傳入的型別不是限定型別及其子類,編譯不成功
- 使用extends萬用字元表示可以讀,不能寫
下界萬用字元 <? super E>
下界用super
進行宣告,表示引數化的型別可能是所指定的型別及其父型別,直至Object
。susper
關鍵字和extends
關鍵字功能相反,使用<? super Integer>
萬用字元表示:
- 允許呼叫set(? super Integer)方法傳入Integer的引用;
- 不允許呼叫get()方法獲得Integer的引用。
唯一例外是可以獲取Object的引用:Object o = p.getxxx()
。
換句話說,使用<? super Integer>萬用字元作為方法引數,表示方法內部程式碼對於引數只能寫,不能讀。
對比extends和super萬用字元區別:
<? extends T>
允許呼叫讀方法T get()
獲取T
的引用,但不允許呼叫寫方法set(T)
傳入T
的引用(傳入null
除外)<? super T>
允許呼叫寫方法set(T)
傳入T
的引用,但不允許呼叫讀方法T get()
獲取T
的引用(獲取Object
除外)。
一個是允許讀不允許寫,另一個是允許寫不允許讀
泛型的擦除
本作品採用《CC 協議》,轉載必須註明作者和本文連結