JAVA 泛型中的通配符 T,E,K,V,?你都弄懂了嗎?我都總結在這

分享阿里 P8 高級架構師吐血總結的 《Java 核心知識體系&面試資料.pdf》

據說是阿里 P8 級高級架構師吐血總結的一份 Java 核心知識.pdf, 內容覆蓋很廣,Java 核心基礎、Java 多線程、高併發、Spring、微服務、Netty 與 RPC、Zookeeper、Kafka、RabbitMQ、Habase、設計模式、負載均衡、分佈式緩存、Hadoop、Spark、Storm、雲計算等。

另外,附送 100G 學習、面試視頻文檔喲~

獲取方式:【關注 + 轉發】後,私信我,回覆關鍵字【資源】,即可免費無套路獲取哦~

以下是資源的部分目錄以及內容截圖:

JAVA 泛型中的通配符 T,E,K,V,?你都弄懂了嗎?我都總結在這

JAVA 泛型中的通配符 T,E,K,V,?你都弄懂了嗎?我都總結在這

JAVA 泛型中的通配符 T,E,K,V,?你都弄懂了嗎?我都總結在這

JAVA 泛型中的通配符 T,E,K,V,?你都弄懂了嗎?我都總結在這

JAVA 泛型中的通配符 T,E,K,V,?你都弄懂了嗎?我都總結在這

重要的事再說一遍,獲取方式:【關注 + 轉發】後,私信我,回覆關鍵字【資源】,即可免費無套路獲取哦~

正文開始

前言

Java 泛型(generics)是 JDK 5 中引入的一個新特性, 泛型提供了編譯時類型安全檢測機制,該機制允許開發者在編譯時檢測到非法的類型。

泛型的本質是參數化類型,也就是說所操作的數據類型被指定為一個參數。

泛型帶來的好處

在沒有泛型的情況的下,通過對類型 Object 的引用來實現參數的“任意化”,“任意化”帶來的缺點是要做顯式的強制類型轉換,而這種轉換是要求開發者對實際參數類型可以預知的情況下進行的。對於強制類型轉換錯誤的情況,編譯器可能不提示錯誤,在運行的時候才出現異常,這是本身就是一個安全隱患。

那麼泛型的好處就是在編譯的時候能夠檢查類型安全,並且所有的強制轉換都是自動和隱式的。

public class GlmapperGeneric {
\t\tprivate T t;
public void set(T t) { this.t = t; }
public T get() { return t; }

public static void main(String[] args) {
// do nothing
}

/**
* 不指定類型
*/
public void noSpecifyType(){
GlmapperGeneric glmapperGeneric = new GlmapperGeneric();
glmapperGeneric.set("test");
// 需要強制類型轉換
String test = (String) glmapperGeneric.get();
System.out.println(test);
}
/**
* 指定類型
*/
public void specifyType(){
GlmapperGeneric<string> glmapperGeneric = new GlmapperGeneric();
glmapperGeneric.set("test");
// 不需要強制類型轉換
String test = glmapperGeneric.get();
System.out.println(test);
}
}
複製代碼
/<string>

上面這段代碼中的 specifyType 方法中 省去了強制轉換,可以在編譯時候檢查類型安全,可以用在類,方法,接口上。

泛型中通配符

我們在定義泛型類,泛型方法,泛型接口的時候經常會碰見很多不同的通配符,比如 T,E,K,V 等等,這些通配符又都是什麼意思呢?

常用的 T,E,K,V,?

本質上這些個都是通配符,沒啥區別,只不過是編碼時的一種約定俗成的東西。比如上述代碼中的 T ,我們可以換成 A-Z 之間的任何一個 字母都可以,並不會影響程序的正常運行,但是如果換成其他的字母代替 T ,在可讀性上可能會弱一些。

通常情況下,T,E,K,V,? 是這樣約定的:

  • ? 表示不確定的 java 類型
  • T (type) 表示具體的一個java類型
  • K V (key value) 分別代表java鍵值中的Key Value
  • E (element) 代表Element

無界通配符

先從一個小例子看起,原文在 這裡 。

我有一個父類 Animal 和幾個子類,如狗、貓等,現在我需要一個動物的列表,我的第一個想法是像這樣的:

List<animal> listAnimals
複製代碼
/<animal>

但是老闆的想法確實這樣的:

List extends Animal> listAnimals
複製代碼

為什麼要使用通配符而不是簡單的泛型呢?通配符其實在聲明局部變量時是沒有什麼意義的,但是當你為一個方法聲明一個參數時,它是非常重要的。

static int countLegs (List extends Animal > animals ) {
int retVal = 0;
for ( Animal animal : animals )
{
retVal += animal.countLegs();
}
return retVal;
}
static int countLegs1 (List< Animal > animals ){
int retVal = 0;
for ( Animal animal : animals )
{
retVal += animal.countLegs();
}
return retVal;
}
public static void main(String[] args) {
List dogs = new ArrayList<>();
\t// 不會報錯
countLegs( dogs );
\t// 報錯
countLegs1(dogs);
}
複製代碼

當調用 countLegs1 時,就會飄紅,提示的錯誤信息如下:

JAVA 泛型中的通配符 T,E,K,V,?你都弄懂了嗎?我都總結在這

所以,對於不確定或者不關心實際要操作的類型,可以使用無限制通配符(尖括號裡一個問號,即 > ),表示可以持有任何類型。像 countLegs 方法中,限定了上屆,但是不關心具體類型是什麼,所以對於傳入的 Animal 的所有子類都可以支持,並且不會報錯。而 countLegs1 就不行。

上界通配符 < ? extends E>

上屆:用 extends 關鍵字聲明,表示參數化的類型可能是所指定的類型,或者是此類型的子類。

在類型參數中使用 extends 表示這個泛型中的參數必須是 E 或者 E 的子類,這樣有兩個好處:

  • 如果傳入的類型不是 E 或者 E 的子類,編譯不成功
  • 泛型中可以使用 E 的方法,要不然還得強轉成 E 才能使用
private  E test(K arg1, E arg2){
E result = arg2;
arg2.compareTo(arg1);
//.....
return result;
}
複製代碼

類型參數列表中如果有多個類型參數上限,用逗號分開

下界通配符 < ? super E>

下界: 用 super 進行聲明,表示參數化的類型可能是所指定的類型,或者是此類型的父類型,直至 Object

在類型參數中使用 super 表示這個泛型中的參數必須是 E 或者 E 的父類。

private  void test(List super T> dst, List src){
for (T t : src) {
dst.add(t);
}
}
public static void main(String[] args) {
List dogs = new ArrayList<>();
List<animal> animals = new ArrayList<>();
new Test3().test(animals,dogs);
}
// Dog 是 Animal 的子類
class Dog extends Animal {
}
複製代碼
/<animal>

dst 類型 “大於等於” src 的類型,這裡的“大於等於”是指 dst 表示的範圍比 src 要大,因此裝得下 dst 的容器也就能裝 src 。

? 和 T 的區別

JAVA 泛型中的通配符 T,E,K,V,?你都弄懂了嗎?我都總結在這

?和 T 都表示不確定的類型,區別在於我們可以對 T 進行操作,但是對 ? 不行,比如如下這種 :

// 可以
T t = operate();
// 不可以
? car = operate();
複製代碼

簡單總結下:

T 是一個 確定的 類型,通常用於泛型類和泛型方法的定義,?是一個 不確定 的類型,通常用於泛型方法的調用代碼和形參,不能用於定義類和泛型方法。

區別1:通過 T 來 確保 泛型參數的一致性

// 通過 T 來 確保 泛型參數的一致性
public void
test(List dest, List src)
//通配符是 不確定的,所以這個方法不能保證兩個 List 具有相同的元素類型
public void
test(List extends Number> dest, List extends Number> src)
複製代碼

像下面的代碼中,約定的 T 是 Number 的子類才可以,但是申明時是用的 String ,所以就會飄紅報錯。

JAVA 泛型中的通配符 T,E,K,V,?你都弄懂了嗎?我都總結在這

不能保證兩個 List 具有相同的元素類型的情況

GlmapperGeneric<string> glmapperGeneric = new GlmapperGeneric<>();
List<string> dest = new ArrayList<>();
List<number> src = new ArrayList<>();
glmapperGeneric.testNon(dest,src);
複製代碼
/<number>/<string>/<string>

上面的代碼在編譯器並不會報錯,但是當進入到 testNon 方法內部操作時(比如賦值),對於 dest 和 src 而言,就還是需要進行類型轉換。

區別2:類型參數可以多重限定而通配符不行

JAVA 泛型中的通配符 T,E,K,V,?你都弄懂了嗎?我都總結在這

使用 & 符號設定多重邊界(Multi Bounds),指定泛型類型 T 必須是 MultiLimitInterfaceA 和 MultiLimitInterfaceB 的共有子類型,此時變量 t 就具有了所有限定的方法和屬性。對於通配符來說,因為它不是一個確定的類型,所以不能進行多重限定。

區別3:通配符可以使用超類限定而類型參數不行

類型參數 T 只具有 一種 類型限定方式:

T extends A
複製代碼

但是通配符 ? 可以進行 兩種限定:

? extends A
? super A
複製代碼

Class 和 Class> 區別

前面介紹了 ? 和 T 的區別,那麼對於,Class 和 <class> 又有什麼區別呢?/<class>

Class 和 Class>

最常見的是在反射場景下的使用,這裡以用一段發射的代碼來說明下。

// 通過反射的方式生成 multiLimit 
// 對象,這裡比較明顯的是,我們需要使用強制類型轉換
MultiLimit multiLimit = (MultiLimit)
Class.forName("com.glmapper.bridge.boot.generic.MultiLimit").newInstance();
複製代碼

對於上述代碼,在運行期,如果反射的類型不是 MultiLimit 類,那麼一定會報 java.lang.ClassCastException 錯誤。

對於這種情況,則可以使用下面的代碼來代替,使得在在編譯期就能直接 檢查到類型的問題:

JAVA 泛型中的通配符 T,E,K,V,?你都弄懂了嗎?我都總結在這

Class 在實例化的時候,T 要替換成具體類。Class> 它是個通配泛型,? 可以代表任何類型,所以主要用於聲明時的限制情況。比如,我們可以這樣做申明:

// 可以
public Class> clazz;
// 不可以,因為 T 需要指定類型
public Class clazzT;
複製代碼

所以當不知道定聲明什麼類型的 Class 的時候可以定義一 個Class>。

JAVA 泛型中的通配符 T,E,K,V,?你都弄懂了嗎?我都總結在這

那如果也想 public Class clazzT; 這樣的話,就必須讓當前的類也指定 T ,

public class Test3 {
public Class> clazz;
// 不會報錯
public Class clazzT;
複製代碼

小結

本文零碎整理了下 JAVA 泛型中的一些點,不是很全,僅供參考。如果文中有不當的地方,歡迎指正。


分享到:


相關文章: