天天看點

List<? extends T>與List<? super T>的差別

List<? extends T>與List<? super T>的差別​

這個問題,初級工程師在做面試題時會出現。進階工程師很少會遇到,但實際上,不少工作了 5 年左右的工程師也回答的不是很好。基于此,整理了本文,分享給大家!

List&lt;? extends T&gt;與List&lt;? super T&gt;的差別

我們先來看一下名詞解釋:

(1)?

?表示類型通配符,即具體傳什麼參數類型,在List定義時不用考慮。

(2)​

​<T>​

這裡的 <> 表示泛型,T 表示泛型中裝載的類型為T類型,等到需要的時候,我們可以具體這個 T。我們在使用動态數組實作 ArrayList 的時候,如果希望這個 ArrayList 不僅僅支援一個類型的話,我們可以給這個 ArrayList 定義泛型,泛型中存放的是T類型。在實際建立出這個 ArrayList 對象時,我們可以指定泛型中的具體類型。

(3)​

​<? extends T>​

類型上界,這裡的 ? 可以是 T 類型或者 T 的子類類型。

(4)​

​<? super T>​

類型下界,這裡的?可以是T類型或者T的超類類型,但不代表我們可以往裡面添加任意超類類型的元素。

List&lt;? extends T&gt;與List&lt;? super T&gt;的差別

在List中引入通配符界限限制的假設​

不管是​

​List<? extends T>​

​還是​

​List<? super T>​

​,如果能讀取元素,那麼這個元素一定能轉化為 T 類型,注意不是強制類型轉換,強制類型轉換是容易出現問題。

顯然​

​List<? extends T>​

​内都是 T 的子類類型,能夠向上轉型為 T 類型,是以該 list 可以讀取。

而​

​List<? super T>​

​内可以是 T 的超類類型,T 的超類轉 T 是有可能出現異常的。

那我幹脆轉化成 Object 類型不好嗎,所有類的基類都是 Object,不屬于強制類型轉換。哥們,轉換成 Object 了,那你還圖個啥?轉換為 Object 類型是沒有意義的。

假設​

​List<? extends T>​

​能添加元素,那麼需要滿足添加的任意元素需要能夠直接轉化成 T 的任何一個子類,T 的子類 A 和子類 B 是不能互相轉化的,顯然該 list 是不能添加元素的。

假設​

​List<? super T>​

​能添加元素,那麼同樣需要滿足添加的任意元素能夠直接轉化成 T 的任何一個超類。此時添加 T 的子類元素就能滿足該要求,因為 T 的任意子類可以向上轉型成 T 的任何超類。

List<? extends T>​

​List<? extends T>​

​是被設計用來讀取資料的泛型,并且隻能讀取類型為 T 的元素。原因如下:

元素是可以進行向上轉型的,是以,我們可以這樣做來讀取元素。

List<? extends Number> list = new ArrayList<>(); 
Number number = list.get(0);      

可以讀取,但不能寫入,比如以下的代碼就直接報錯。

public class Main {

    static class A { }

    static class B extends A { }

    static class C extends A { }

    public static void main(String[] args){
        List<? extends A> list = new ArrayList<>();
        list.add(new A());//編譯報錯
        list.add(new B());//編譯報錯
        list.add(new C());//編譯報錯
    }

}      

A 的子類 B 與子類 C 是不能互相轉換的,是以是不能往該 list 中添加元素。

雖然不能添加元素,但可以在初始化的時候,接受一個已經定義好的 list,而該 list 存放的類型一定相同。是以,​

​List<? extends T>​

​可直接接受一個定義好的 list。

public static List<Integer> getList(){
    List<Integer> list=new ArrayList<>();
    list.add(1);
    return list;
}

// ....

public static void main(String[] args){
    List<? extends Number> list = new ArrayList<>();
    list=getList();
}      

List<? super T>​

​List<? super T>​

​是被設計用來添加資料的泛型,并且隻能添加 T 類型或其子類類型的元素。

為什麼隻能是 T 類型及其子類型元素,超類類型的元素不可以嗎?

超類類型轉化為 T 類型,是需要強制類型轉換的,是容易出現異常的,無法保障的。

而傳入 T 類型及其子類類型時,能夠直接轉化為 T 的任意超類類型。比如,下面的代碼是可以運作的

public class Main {

    static class A { }

    static class B extends A { }

    static class C extends A { }

    public static void main(String[] args){
        List<? super A> list = new ArrayList<>();
        list.add(new A());
        list.add(new B());
        list.add(new C());
    }
}      

該 list 也可以讀取其中的元素,從第二節可以得出,隻能用 Object 接收,沒多大意義。

List<? super Integer> list2 = new ArrayList<>();
list2.add(new Integer(1));
Object integer=list2.get(0);      

如果我們使用 Object 類型來接收擷取到的元素,那麼元素本身的類型就會丢失,是以,我們不使用​

​List<? super T>​

​來擷取元素。

如果我們非要使用​

​List<? super Integer>​

​中的 Integer 類型來接收擷取到的元素,那麼必須進行強制類型轉換,是會出現異常的,無法保障。

List<? super Integer> list2 = new ArrayList<>();
list2.add(new Integer(1));
Integer integer1= (Integer) list2.get(0);      

總結​

(1)​

​List<? extends T>​

​适用于讀取資料,讀取出來的資料全部用T類型接收。如果我們往此 list 中添加 T 類型不同的子類的話,各種子類無法互相轉換,是以不能添加元素,但可接受初始指派。

(2)​

​List<? super T>​

​适用于添加元素,隻能添加 T 類型或其子類類型。因為這些類型都能轉換為T的任意超類類型(向上轉型),是以我們可以對此 list 添加元素。隻能用 Object 類型來接收擷取到的元素,但是這些元素原本的類型會丢失。

更加通俗易懂的例子​

什麼,你還沒明白?那我舉一些直覺的例子。

List&lt;? extends T&gt;與List&lt;? super T&gt;的差別

注意:​

​向上轉型是安全的,向下轉型是不安全的,除非你知道 List 中的真實類型,否則向下轉型就會報錯​

​。

extends

​List<? extends Number> foo3​

​意味着下面的指派語句都是合法的:

List<? extends Number> foo3 = new ArrayList<Number>();  // Number "extends" Number (in this context)
List<? extends Number> foo3 = new ArrayList<Integer>(); // Integer extends Number
List<? extends Number> foo3 = new ArrayList<Double>();  // Double extends Number      
  1. 讀取

給定上述可能的指派語句,能保證你從​

​List foo3​

​中取出什麼樣類型的對象?

  • 你可以讀取一個

    Number

    對象,因為上面任意一個list都包含

    Number

    對象或者

    Number

    子類的對象(上面的Number、Integer、Double都可以轉型成Number,并且是安全的,是以讀取總是可以的)。如下代碼就不會報錯:
List<? extends Number> foo4 = new ArrayList<Integer>();
Number number = foo4.get(0);      
  • 你不能讀取一個

    Integer

    對象,因為

    foo3

    可能指向的是

    List<Double>

    (與其運作時發現Double轉成Integer報錯,不如編譯時就不讓從

    foo3

    中取

    Integer

    對象)。如下代碼編譯時會報

    Incompatible types

    錯的:
List<? extends Number> foo4 = new ArrayList<Integer>();
Integer number = foo4.get(0);      

因為編譯的時候編譯器隻知道foo4引用是一個List<? extends Number>,要到運作時才會綁定到new ArrayList(),是以編譯的時候是無法判斷foo4指向的List中到底是什麼類型,唯一能确定的就是這個類型是Number的子類(或者就是Number類)。

  • 你也不能讀取一個

    Double

    對象,因為

    foo3

    可能指向的是

    List<Integer>

  1. 寫入

給定上述可能的指派語句,你能往​

​List foo3​

​中添加什麼類型的對象進而保證它對于所有可能的​

​ArrayList​

​都是合法的呢?

  • 你不能添加一個

    Integer

    對象,因為

    foo3

    可能指向的是

    List<Double>

    。如下代碼是會編譯報錯的:
List<? extends Number> foo4 = new ArrayList<Integer>();
foo4.add(new Integer(1));      

因為編譯期間是無法知道foo4指向的ArrayList中到底放的是什麼類型,隻有到運作時才知道(就是Java所謂的晚綁定或運作時綁定)。與其到運作時發現往一個ArrayList中add一個Integer導緻抛出類型轉換異常,倒不如編譯時就報錯,即使ArrayList中放的就是Integer類型。

  • 你不能添加一個

    Double

    對象,因為

    foo3

    可能指向的是

    List<Integer>

  • 你不能添加一個

    Number

    對象,因為

    foo3

    可能指向的是

    List<Integer>

「總結一下」:你不能往​

​List<? extends T>​

​中添加任何對象,因為你不能保證​

​List​

​真正指向哪個類型,是以不能确定添加的對象就是​

​List​

​所能接受的類型。能保證的,僅僅是你可以從​

​List​

​中讀取的時候,你獲得的肯定是一個​

​T​

​類型的對象(即使是​

​T​

​類型的子類對象也是​

​T​

​類型的)。

super

現在考慮​

​List<? super T>​

​包含通配符的聲明

List<? super Integer> foo3

意味着下面任何一個指派語句都是合法的:

List<? super Integer> foo3 = new ArrayList<Integer>();  // Integer is a "superclass" of Integer (in this context)
List<? super Integer> foo3 = new ArrayList<Number>();   // Number is a superclass of Integer
List<? super Integer> foo3 = new ArrayList<Object>();   // Object is a superclass of Integer      
  1. 讀取
  • 你不能保證是一個

    Integer

    對象,因為

    foo3

    可能指向一個

    List<Number>

    或者

    List<Object>

  • 你不能保證是一個

    Number

    對象,因為

    foo3

    可能指向一個

    List<Object>

  • 你能保證的僅僅是它一定是一個

    Object

    類的執行個體或者

    Object

    子類的執行個體(但是你不知道到底是哪個子類)。
  1. 寫入
  • 你可以添加一個

    Integer

    執行個體,因為

    Integer

    類型對于上述所有的list都是合法的。
  • 你可以添加任何

    Integer

    子類的執行個體,因為一個

    Integer

    子類的執行個體都可以向上轉型成上面清單中的元素類型。
  • 你不可以添加

    Double

    類型,因為

    foo3

    可能指向的是

    ArrayList<Integer>

  • 你不可以添加

    Number

    類型,因為

    foo3

    可能指向的是

    ArrayList<Integer>

  • 你不可以添加

    Object

    類型,因為

    foo3

    可能指向的是

    ArrayList<Integer>

PECS

  • "Producer Extends"的意思是,如果你需要一個

    List

    去生産

    T

    類型values(也就是說你需要去list中讀取

    T

    類型執行個體),你需要聲明這個

    List

    中的元素為

    ? extends T

    ,例如

    List<? extends Integer>

    ,但是你不能往裡面添加元素。
  • "Consumer Super"的意思是,如果你需要一個

    List

    去消費

    T

    類型values(也就是說你需要往list中添加

    T

    類型執行個體),你需要聲明這個

    List

    中的元素為

    ? super T

    ,例如

    List<? super Integer>

    。但是不能保證你從這個list中讀取出來對象類型。
  • 如果你既需要往list中寫,也需要從list中讀,那麼你就不能用通配符

    ?

    ,必須用精确的類型,比如

    List<Integer>