Java泛型中<?> 和 <? extends Object>的異同分析

鍋外的大佬發表於2020-11-18

相信很多人和我一樣,接觸Java多年,卻仍舊搞不清楚 Java 泛型中 <?><? extends Object>的相似和不同。但是,這應該是一個比較高階大氣上檔次的Question, 在我們進行深入的探討之前,有必要對Java泛型有一個基礎的瞭解。詳細請看上一篇文章!
重溫Java泛型,帶你更深入地理解它,更好的使用它!

1. 泛型產生的背景

在 JDK5 中引入了泛型來消除編譯時錯誤和加強型別安全性。這種額外的型別安全性消除了某些用例中的強制轉換,並使程式設計師能夠編寫泛型演算法,這兩種方法都可以生成更具可讀性的程式碼。

例如,在 JDK5 之前,我們必須使用強制轉換來處理列表的元素。這反過來又產生了一類特定的執行時錯誤:

List aList = new ArrayList();
aList.add(new Integer(1));
aList.add("a_string");
        
for (int i = 0; i < aList.size(); i++) {
    Integer x = (Integer) aList.get(i);
}

現在,我們想解決兩個問題:

  • 我們需要一個顯式轉換來從 aList 中提取值——型別取決於左側的變數型別(在本例中為Integer
  • 當我們試圖將 a_string 轉換為 Integer 時,在第二次迭代中會出現執行時錯誤。

泛型填補了這個空白,程式碼如下:

List<Integer> iList = new ArrayList<>();
iList.add(1);
iList.add("a_string"); // compile time error
 
for (int i = 0; i < iList.size(); i++) {
    int x = iList.get(i);
}

執行上述程式碼,編譯器會告訴我們,無法將 a_string 新增到 Integer 型別的 List 中,這比起在執行時才發現異常要好很多。
而且,不需要顯式轉換,因為編譯器已經知道 iList 包含 Integer型別的資料。另外,由於自動拆箱的關係,我們甚至不需要使用 Integer 型別,它的原始型別就足夠了。

2. 泛型中的萬用字元

問號或萬用字元在泛型中用來表示未知型別。它可以有三種形式:

  • 無界萬用字元: List<?> 表示未知型別的列表
  • 上界萬用字元:List<? extends Number> 表示 Number 或其子型別(如IntegerDouble)的列表
  • 下界萬用字元:List<? super Integer> 表示Integer或其超型別NumberObject的列表

由於 Object 是 Java 中所有型別的固有超類,所以我們會認為它也可以表示未知型別。換句話說,List<?>List<Object> 可以達到相同的目的。但事實並非如此。

來看看這兩個方法:

public static void printListObject(List<Object> list) {    
    for (Object element : list) {        
        System.out.print(element + " ");    
    }        
}    
 
public static void printListWildCard(List<?> list) {    
    for (Object element: list) {        
        System.out.print(element + " ");    
    }     
}

給出一個整數的列表,比如:

List<Integer> li = Arrays.asList(1, 2, 3);

執行 printListObject(li) 不會編譯,並且我們將得到以下錯誤:

The method printListObject(List<Object>) is not applicable for the arguments (List<Integer>)

而執行 printListWildCard(li) 將通過編譯,並將 1 2 3 輸出到控制檯。

3. <?>和<? extends Object>的相同之處

在上面的示例中,如果我們將 printListWildCard 方法更改為:

public static void printListWildCard(List<? extends Object> list)

它的工作方式與 printListWildCard(List<?>)相同。這是因為 Object 是 Java 所有物件的超類,基本上所有的東西都擴充套件了Object。因此,這個方法也會處理一個 Integer 型別的List。

也就是說, <?> 和 <? extends Object> 在這個例子中是同一個意思。

雖然在大多數情況下,這是正確的,但也有一些區別。接下來我們就來看看它們之間的差異。

4. <?>和<? extends Object>的不同之處

可重構型別是指那些在編譯時未被擦除的型別。換句話說,一個不可重構型別,執行時將比編譯時表達的資訊更少,因為其中一些資訊會被擦除。

一般來說,引數化型別是不可重新定義的。比如 List<String>Map<Integer,String> 就不可重新定義。編譯器會擦除它們的型別,並將它們分別視為列表和對映。

這個準則的唯一例外是無界萬用字元型別。也就是說, List<?> 以及 Map<?, ?> 是可重寫的。

另外,List<? extends Object> 不可重寫。雖然微妙,但這是一個顯著的區別。

不可重構的型別在某些情況下不能使用,例如在 instanceof 運算子或作為陣列的元素。

所以,如果我們的程式碼寫成這樣:

List someList = new ArrayList<>();
boolean instanceTest = someList instanceof List<?>

程式碼編譯後,instanceTesttrue
但是,如果我們在 List<? extends Object> 上使用 instanceof 運算子:

List anotherList = new ArrayList<>();
boolean instanceTest = anotherList instanceof List<? extends Object>;

那麼第2行不編譯。
類似地,在下面的程式碼片段中,第1行編譯,但第2行不編譯:

List<?>[] arrayOfList = new List<?>[1];
List<? extends Object>[] arrayOfAnotherList = new List<? extends Object>[1]

5.結語

好了,文章到此就劃上句號了,在本文中,我們主要討論了<?> 和 <? extends Object>的異同,雖然基本上是相似的,但兩者在可變與否方面存在細微差異。
如果你覺得文章還不錯,記得關注公眾號: 鍋外的大佬
劉一手的部落格

相關文章