Java中類型參數“T”和無界通配符“?”的區別

Java 編譯器 斯蒂芬·庫裡 Java技術架構 2019-04-06

專注於Java領域優質技術號,歡迎關注

作者:JavaEdge

首先要區分開兩種不同的場景

  • 聲明一個泛型類或泛型方法

類型參數“<T>”主要用於第一種,聲明泛型類或泛型方法

  • 使用泛型類或泛型方法

無界通配符“<?>”主要用於第二種,使用泛型類或泛型方法

1 <T>聲明泛型類的類型參數

List<T>最應該出現的地方,應該是定義一個泛型List容器

但List是庫裡自帶的容器,看看ArrayList的源碼頭一行:

Java中類型參數“T”和無界通配符“?”的區別

ArrayList<E>中的“E”也是類型參數。只是表示容器中元素Element的時候,習慣用“E”

換一個簡單的例子,我們自己定義一個新泛型容器叫Box<T>。

Java中類型參數“T”和無界通配符“?”的區別

為什麼這裡要用類型參數?因為這是一種”約束“,為了保證Box裡的item1, item2都是同一個類型T。Box<String>,代表兩個item都是String。Box<Integer>裡兩個item都是Integer。

List容器庫裡都幫我們寫好了,所以我們是不會去定義List<T>的

那什麼時候會出現List<T>

要麼是作為泛型類的成員字段或成員方法的參數間接出現。還是剛才Box<T>的例子,

Java中類型參數“T”和無界通配符“?”的區別

現在Box類裡有三個地方出現了List<T>:

  • 成員字段item的類型
  • get( )方法的返回值
  • set( )方法的參數

這裡寫成List<T>為了表示和Box<T>類型參數保持一致

2 <T>聲明泛型方法

另外一種會出現List<T>的地方是泛型方法

比如Function類的reduce是個靜態泛型方法,負責對列表裡的所有元素求和

這裡的List<T>出現在參數,函數返回值和函數內部,也是為了保持泛型類型的一致性

Java中類型參數“T”和無界通配符“?”的區別

3 聲明泛型類不能用無界通配符<?>

反觀List<?>,首先要明確通配符不能拿來聲明泛型

像下面這樣用通配符"?"來表示類型參數的約束是不行的

Java中類型參數“T”和無界通配符“?”的區別

Error Example

通配符是拿來使用定義好的泛型的

比如用<?>聲明List容器的變量類型,然後用一個實例對象給它賦值的時候就比較靈活。

Java中類型參數“T”和無界通配符“?”的區別

4 <?>的坑

List<?>這個寫法非常坑。因為,這時候通配符會捕獲具體的String類型,但編譯器不叫它String,而是起個臨時的代號,比如”capture#1“

所以以後再也不能往list裡存任何元素,包括String,唯一能存的就是null

Java中類型參數“T”和無界通配符“?”的區別

Java中類型參數“T”和無界通配符“?”的區別

另外如果拿List<?>做參數,也會有奇妙的事情發生。還是剛才Box<T>的例子,有get()和set()兩個方法,一個存,一個取。

Java中類型參數“T”和無界通配符“?”的區別

新的getSet()方法,只是把item先用get()方法讀出來,然後再用set()方法存回去。按理說不可能有問題。實際運行卻會報錯。

error: incompatible types: Object cannot be converted to capture#1

原因和前面一樣,通配符box<?>.set()的參數類型被編譯器捕獲,命名為capture#1,和box<?>.get()返回的Object對象無法匹配

解決方法,是要給getSet()方法寫一個輔助函數

Java中類型參數“T”和無界通配符“?”的區別

5. 有界通配符<? extends XXX>,<? super XXX>

實際更常用的是<? extends XXX>或者<? super XXX>兩種,帶有上下界的通配符

鏈接:https://www.jianshu.com/p/63f40784bc6a

相關推薦

推薦中...