快速理解<? extends T>和<? super T>

更新于 2020.9.1

在 Java 泛型中存在通配符的概念:

  • <? extends T>:上界通配符(Upper Bounds Wildcards)

  • <? super T>:下界通配符(Lower Bounds Wildcards)

通配符存在的意義

有類就會有繼承,在開發(fā)中我們經(jīng)常接觸泛型,肯定希望在使用泛型的時候可以兼容其子類,但是在使用集合或者其他類似的情景時會出現(xiàn)問題。下面用集合例子模擬真實的需求:

A a = new B();
ArrayList<A> list= new ArrayList<B>(); // 報錯

根據(jù)上面的代碼,假設(shè) B 繼承 A,第一行代碼是合法的,但是第二行代碼編譯器會直接紅線報錯,ArrayList<B> 無法賦值給 ArrayList<A>。這是因為編譯器在確定 ArrayList 元素類型的時候不會考慮 A 和 B 的繼承關(guān)系,編譯器無法承認(rèn)第二行代碼是合法的。為了解決這個問題引入了通配符的概念。

Ps:該例子中用的是 ArrayList 做例子,但并不是只針對集合,是針對類似集合出現(xiàn)的問題的情景。

類繼承關(guān)系圖.png

根據(jù)上面的類的繼承關(guān)系圖,我們用 C 來觀察,假設(shè)有下面兩個 List:

List<? extends C> list1; // list1 的元素的類型只能是 C 和 C 的子類。
List<? super C> list2; // list2 的元素的類型只能是 C 和 C 的父類。

list1 和 list2 的類型區(qū)間:

ArrayList<B> b = new ArrayList<>();
ArrayList<C> c = new ArrayList<>();
ArrayList<D> d = new ArrayList<>();
// <? extends C>
list1 = b; // 報錯
list1 = c;
list1 = d;
// <? super C>
list2 = b; 
list2 = c;
list2 = d; // 報錯

注意: 編譯器針對 <? extends C> 和 <? super C> 只關(guān)心與 C 有關(guān)的類繼承關(guān)系,即只關(guān)心 C 的所有父類、 C 的所有子類和 C 本身的繼承關(guān)系,即使 A 或者 B 存在其他子類編譯器都不關(guān)心。

下面先認(rèn)識 <? extends C> 和 <? super C> 的區(qū)別:

  • <? extends C>

    • add(): 不可以加入任何元素(除了 null 因為 null 可以為任何類型),編譯器會紅線報錯,如:

      • /**
         * Required type: capture of ? extends C
         * Provided: B
         */
        // list1.add(new B());
        // list1.add(new C());
        // list1.add(new D());
        list1.add(null);
        
  • get():返回的值的類型默認(rèn)是 C ,如:

    • C c = list1.get(0);
      /**
       * 編譯器不報錯,但如果 list1 第0個元素是 E 或者 F 的話,
       * 運行時存在強制類型轉(zhuǎn)換失敗程序崩潰的風(fēng)險
       * Exception in thread "main" java.lang.ClassCastException: 
       * E cannot be cast to D
       */
      D d1 = (D) list.get(0);
      
  • <? super C>

    • add(): 只能添加類型為 C 和其子類類型的元素,如:

      • /**
         * Required type: capture of ? super C
         * Provided: B
         */
        // list2.add(new B()); // 報錯
        list2.add(new C());
        

      list2.add(new D());
      list2.add(new E());
      list2.add(new F());

      
      
    • get():返回的值的類型默認(rèn)是 Object,如:

      • Object object = list2.get(0);
        

簡單來說就是 <? extends C> 上界為 C 類型范圍粗略理解為 [ C , +∞ ),不允許添加除 null 的元素,get() 的元素類型默認(rèn)是 C ,當(dāng)然在知道集合元素類型的情況下通過強轉(zhuǎn)程序是允許的;

<? super C> 下界為 C 類型范圍粗略理解為 ( -∞ , C ],允許添加 C 以及 C 的子類類型的元素,獲取的元素類型是 Object


從編譯器的角度理解上面的區(qū)別:

首先理解車的例子:單車是車的子類,單車有車的特性可以代表車,但是車不能代表單車。即類型向上兼容向下不兼容。

  • <? extends C>
    • add() : 編譯器只知道元素類型是 C 或 C 的子類,所以有可能是 C D E F 其中一個類型,為保證類型安全不能添加除了 null 以外的任何元素,即使是 C 本身也不行(車不能代表單車).
    • get() : 既然編譯器不知道此時集合中的元素是 C D E F 的哪一個,返回類型只能是他們共同父類 C 類型.
  • <? super C>
    • add() : 編譯器只知道元素類型是 C 或者 C 的父類,所以有可能是 A B C 其中一個類型。編譯器知道下界是 C ,根據(jù)類型向上兼容所以可以添加的元素是 C 以及 C 的子類(單車可以代表車).
    • get() : 既然編譯器不確定集合類型是 A B C 的哪一種,返回類型只能是他們的共同父類 Object .

特點:<? extends C> 的 add() 被限制,<? super C> 的 get() 被限制


PECS 原則

<? extends C> 適合大量做獲取操作的情景,<? super C> 適合大量做添加操作的情景。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容