Java 之泛型通配符 ? extends T 與 ? super T 解惑

簡述

你們在平時的工做學習中, 確定會見過很多以下的語句:java

List<? super T>
List<? extends T>

咱們都知道, 上面的代碼時關於 Java 泛型的, 那麼這兩個不一樣的寫法都有什麼區別呢?學習

首先, 說到 Java 的泛型, 咱們必需要提到的是Java 泛型的類型擦除機制: Java中的泛型基本上都是在編譯器這個層次來實現的. 在生成的 Java 字節代碼中是不包含泛型中的類型信息的. 使用泛型的時候加上的類型參數, 會被編譯器在編譯的時候去掉. 這個過程就稱爲類型擦除. 如在代碼中定義的List<Object>和List<String>等類型, 在編譯以後都會變成List, JVM看到的只是List, 而由泛型附加的類型信息對JVM來講是不可見的.code

在使用泛型類時, 咱們可使用一個具體的類型, 例如能夠定義一個 List<Integer> 的對象, 咱們的泛型參數就是 Integer; 咱們也可使用通配符 ? 來表示一個未知類型, 例如 List<?> 就表示了泛型參數是某個類型, 只不過咱們並不知道它的具體類型時什麼.
List<?>所聲明的就是全部類型都是能夠的, 但須要注意的是, List<?>並不等同於List<Object>. 對於 List<Object> 來講, 它實際上肯定了 List 中包含的是 Object 及其子類, 咱們可使用 Object 類型來接收它的元素. 相對地, List<?> 則表示其中所包含的元素類型是不肯定, 其中可能包含的是 String, 也多是 Integer. 若是它包含了 String 的話, 往裏面添加 Integer 類型的元素就是錯誤的. 做爲對比, 咱們能夠給一個 List<Object> 添加 String 元素, 也能夠添加 Integer 類型的元素, 由於它們都是 Object 的子類.
正由於類型未知, 咱們就不能經過 new ArrayList<?>() 的方法來建立一個新的ArrayList 對象, 由於編譯器沒法知道具體的類型是什麼. 可是對於 List<?> 中的元素, 咱們卻均可以使用 Object 來接收, 由於雖然類型未知, 但確定是Object及其子類.對象

咱們在上面提到了, List<?> 中的元素只能使用 Object 來引用, 這樣做確定時不太方便的, 不過幸運的是, Java 的泛型機制容許咱們對泛型參數的類型的上界和下界作一些限制, 例如 List<? extends Number> 定義了泛型的上界是 Number, 即 List 中包含的元素類型是 Number 及其子類. 而 List<? super Number> 定義了泛型的下界, 即 List 中包含的是 Number 及其父類.
當引入了泛型參數的上界和下界後, 咱們編寫代碼相對來講就方便了許多, 不過也引入了新的問題, 即咱們在何時使用上界, 何時使用下界, 以及它們的區別和限制到底時什麼? 下面我來講說個人理解.get

? extends T

? extends T 描述了通配符上界, 即具體的泛型參數須要知足條件: 泛型參數必須是 T 類型或它的子類, 例如:編譯器

List<? extends Number> numberArray = new ArrayList<Number>();  // Number 是 Number 類型的
List<? extends Number> numberArray = new ArrayList<Integer>(); // Integer 是 Number 的子類
List<? extends Number> numberArray = new ArrayList<Double>();  // Double 是 Number 的子類

上面三個操做都是合法的, 由於 ? extends Number 規定了泛型通配符的上界, 即咱們實際上的泛型必需要是 Number 類型或者是它的子類, 而 Number, Integer, Double 顯然都是 Number 的子類(類型相同的也能夠, 即這裏咱們能夠認爲 Number 是 Number 的子類).io

子類型判斷

假設有類型 G, 以及 SuperClass 和 SubClass 兩個類, 而且 SuperClass 是 SubClass 的父類, 那麼:編譯

  • G<? extends SubClass> 是 G<? extends SuperClass> 的子類型. 如 List<? extends Integer> 是 List<? extends Number> 的子類型class

  • G<SuperClass> 是 G<? extends SuperClass> 的子類型, 例如 List<Integer> 是 List<? extends Integer> 的子類型.泛型

  • G<?> 和 G<? extends Object> 等同.

能夠想象 G<? extends T> 爲一個左閉右開的區間(T 在最左邊), G<? extends Object> 是最大的區間, 當區間 G<? extends SuperClass> 包含 區間 G<? extends SubClass>時, 那麼較大的區間就是父類.

關於讀取

根據上面的例子, 對於 List<? extends Number> numberArray 對象:

  • 咱們可以從 numberArray 中讀取到 Number 對象, 由於 numberArray 中包含的元素是 Number 類型或 Number 的子類型.

  • 咱們不能從 numberArray 中讀取到 Integer 類型, 由於 numberArray 中可能保存的是 Double 類型.

  • 同理, 咱們也不能從 numberArray 中讀取到 Double 類型.

關於寫入

根據上面的例子, 對於 List<? extends Number> numberArray 對象:

  • 咱們不能添加 Number 到 numberArray 中, 由於 numberArray 有多是List<Double> 類型

  • 咱們不能添加 Integer 到 numberArray 中, 由於 numberArray 有多是 List<Double> 類型

  • 咱們不能添加 Double 到 numberArray 中, 由於 numberArray 有多是 List<Integer> 類型

即, 咱們不能添加任何對象到 List<? extends T> 中, 由於咱們不能肯定一個 List<? extends T> 對象實際的類型是什麼, 所以就不能肯定插入的元素的類型是否和這個 List 匹配. List<? extends T> 惟一能保證的是咱們從這個 list 中讀取的元素必定是一個 T 類型的.

? super T

? super T 描述了通配符下界, 即具體的泛型參數須要知足條件: 泛型參數必須是 T 類型或它的父類, 例如:

// 在這裏, Integer 能夠認爲是 Integer 的 "父類"
List<? super Integer> array = new ArrayList<Integer>();
// Number 是 Integer 的 父類
List<? super Integer> array = new ArrayList<Number>();
// Object 是 Integer 的 父類
List<? super Integer> array = new ArrayList<Object>();

關於讀取

對於上面的例子中的 List<? super Integer> array 對象:

  • 咱們不能保證能夠從 array 對象中讀取到 Integer 類型的數據, 由於 array 多是 List<Number> 類型的.

  • 咱們不能保證能夠從 array 對象中讀取到 Number 類型的數據, 由於 array 多是 List<Object> 類型的.

  • 惟一可以保證的是, 咱們能夠從 array 中獲取到一個 Object 對象的實例.

關於寫

對於上面的例子中的 List<? super Integer> array 對象:

  • 咱們能夠添加 Integer 對象到 array 中, 也能夠添加 Integer 的子類對象到 array 中.

  • 咱們不能添加 Double/Number/Object 等不是 Integer 的子類的對象到 array 中.

易混淆點

有一點須要注意的是, List<? super T>List<? extends T> 中, 咱們所說的 XX 是 T 的父類(a superclass of T)XX 是 T 的子類(a subclass of T) 實際上是針對於泛型參數而言的. 例如考慮以下例子:

List<? super Integer> l1 = ...
List<? extends Integer> l2 = ...

那麼這裏 ? super Integer? extends Integer 的限制是對誰的呢? 是表示咱們能夠插入任意的對象 X 到 l1 中, 只要 X 是 Integer 的父類? 是表示咱們能夠插入任意的對象 Y 到 l2 中, 只要 Y 是 Integer 的子類?
其實不是的, 咱們必需要拋棄上面的概念, ? super Integer? extends Integer 限制的實際上是 泛型參數, 即 List<? super Integer> l1 表示 l1 的泛型參數 T 必需要知足 T 是 Integer 的父類, 所以諸如 List<Object>, List<Number 的對象就能夠賦值到 l1 中. 正由於咱們知道了 l1 中的泛型參數的邊界信息, 所以咱們就能夠向 l1 中添加 Integer 對象了, 推理過程以下:

令 T 是 l1 的泛型參數, 即:
    l1 = List<T> = List<? super Integer>
所以有 T 是 Integer 或 Integer 的父類.
若是 T 是 Integer, 則 l1 = List<Integer>, 顯然咱們能夠添加任意的 Integer 對象或 Integer 的子類對象到 l1 中.
若是 T 是 Integer 的父類, 那麼同理, 對於 Integer 或 Integer 的子類的對象, 咱們也能夠添加到 l1 中.

按一樣的分析方式, List<? extends Integer> l2 表示的是 l2 的泛型參數是 Integer 的子類型. 而若是咱們要給一個 List<T> 插入一個元素的話, 咱們須要保證此元素是 T 或是 T 的子類, 而這裏 List<? extends Integer> l2, l2 的泛型參數是什麼類型咱們都不知道, 進而就不能肯定 l2 的泛型參數的子類是哪些, 所以咱們就不能向 l2 中添加任何的元素了.

來一個對比:

  • 對於 List<? super Integer> l1:

    • 正確的理解: ? super Integer 限定的是泛型參數. 令 l1 的泛型參數是 T, 則 T 是 Integer 或 Integer 的父類, 所以 Integer 或 Integer 的子類的對象就能夠添加到 l1 中.

    • 錯誤的理解: ? super Integer限定的是插入的元素的類型, 所以只要是 Integer 或 Integer 的父類的對象均可以插入 l1 中

  • 對於 List<? extends Integer> l2:

    • 正確的理解: ? extends Integer 限定的是泛型參數. 令 l2 的泛型參數是 T, 則 T 是 Integer 或 Integer 的子類, 進而咱們就不能找到一個類 X, 使得 X 是泛型參數 T 的子類, 所以咱們就不能夠向 l2 中添加元素. 不過因爲咱們知道了泛型參數 T 是 Integer 或 Integer 的子類這一點, 所以咱們就能夠從 l2 中讀取到元素(取到的元素類型是 Integer 或 Integer 的子類), 並能夠存放到 Integer 中.

    • 錯誤的理解: ? extends Integer 限定的是插入元素的類型, 所以只要是 Integer 或 Integer 的子類的對象均可以插入 l2 中

使用場景

PECE 原則: Producer Extends, Consumer Super

  • Producer extends: 若是咱們須要一個 List 提供類型爲 T 的數據(即但願從 List 中讀取 T 類型的數據), 那麼咱們須要使用 ? extends T, 例如 List<? extends Integer>. 可是咱們不能向這個 List 添加數據.

  • Consumer Super: 若是咱們須要一個 List 來消費 T 類型的數據(即但願將 T 類型的數據寫入 List 中), 那麼咱們須要使用 ? super T, 例如 List<? super Integer>. 可是這個 List 不能保證從它讀取的數據的類型.

  • 若是咱們既但願讀取, 也但願寫入, 那麼咱們就必須明確地聲明泛型參數的類型, 例如 List<Integer>.

例子:

public class Collections { 
  public static <T> void copy(List<? super T> dest, List<? extends T> src) 
  {
      for (int i=0; i<src.size(); i++) 
        dest.set(i,src.get(i)); 
  } 
}

上面的例子是一個拷貝數據的代碼, src 是 List<? extends T> 類型的, 所以它能夠讀取出 T 類型的數據(讀取的數據類型是 T 或是 T 的子類, 可是咱們不能確切的知道它是什麼類型, 惟一能肯定的是讀取的類型 is instance of T), , dest 是 List<? super T> 類型的, 所以它能夠寫入 T 類型或其子類的數據.

參考

Java深度歷險(五)——Java泛型
difference-between-super-t-and-extends-t-in-java

相關文章
相關標籤/搜索