Java中的上界與下界(? extends/super T)

? 通配符類型

  • <? extends T> 表示類型的上界,? 表示參數化類型的可能是 T 或是 T 的子類
  • <? super T> 表示類型下界,? 表示參數化類型是此 T 類型的超類型 (父類型),直至 Object

上界 <? extends T>不能往裏存,只能往外取

  • 比如,我們現在定義:List<? extends T>首先你很容易誤解它爲繼承於T的所有類的集合,你可能認爲,你定義的這個List可以用來put任何T的子類,那麼我們看下面的代碼:
  • List<? extneds Father> 表示 “具有任何從 Son 繼承類型的列表”,編譯器無法確定 List 所持有的類型,所以無法安全的向其中添加對象。可以添加 null,因爲 null 可以表示任何類型。所以 List 的 add() 不能添加任何有意義的元素,但是可以接受現有的子類型 List 爲賦值。
  • 你也許試圖這樣做:
  • 即使你指明瞭爲 Son 類型,也不能用 add() 添加一個 Son 對象。
  • List 中爲什麼不能加入 Father 類和 Father 類的子類,分析如下:
  • List<? extneds Father> 表示上限是 Father,下面這樣的賦值是合法的,並且如果讓 List<? extends Father> 支持 add() 的話
  • List<? extends Father> list1 = new ArrayList<Father>();
    List<? extends Father> list2 = new ArrayList<Son>();
    List<? extends Father> list3 = new ArrayList<LeiFeng>();
    
    // list1 可以 add Father 和所有 Father 的子類
    // list2 可以 add Son 和所有 Son 的子類
    // list3 可以 add LeiFeng 和所有 LeiFeng 的子類
    
    // 下面代碼還是會編譯不通過:
    list1.add(new Father());    //error
    list1.add(new Son());       //error
  • 原因是編譯器只知道容器內是Father或者它的派生類,但具體是什麼類型不知道。可能是Father?可能是Son?也可能是LeiFeng?編譯器在看到後面用 Father 賦值以後,集合裏並沒有限定參數類型是“Father“。而是標上一個佔位符:CAP#1,來表示捕獲一個 Father 或 Father 的子類,具體是什麼類不知道,代號CAP#1。然後無論是想往裏插入 Son 或者 LeiFeng 或者Father 編譯器都不知道能不能和這個 CAP#1 匹配,所以就都不允許。
  • 所以通配符 <?> 和類型參數的區別就在於,對編譯器來說所有的 T 都代表同一種類型。比如下面這個泛型方法裏,三個T都指代同一個類型,要麼都是 String,要麼都是 Integer。
  • public <T> List<T> fill(T... t);
  • 但通配符 <?> 沒有這種約束,List<?> 單純的就表示:集合裏放了一個東西,是什麼我不知道。
  • 所以這裏的錯誤就在這裏,List<? extneds Father> 裏什麼都放不進去。
  • List<? extends Father> list 不能進行 add(),但是這種形式還是很有用的,雖然不能使用 add(),但是可以在初始化的時候指定不同的類型。比如:
  • List<? extends Father> list1 = getFatherList();
    // getFatherList方法會返回一個Father的子類的list
  • 另外,由於我們已經保證了 List 中保存的是 Father 類或者他的某一個子類,所以,可以用get方法直接獲得值:
  • List<? extends Father> list1 = new ArrayList<>();
    Father father = list1.get(0);  // 讀取出來的東西只能存放在Father或它的基類裏。
    Object object = list1.get(0);  // 讀取出來的東西只能存放在Father或它的基類裏。
    Human human = list1.get(0);    // 讀取出來的東西只能存放在Father或它的基類裏。
    Son son = (Son)list1.get(0);

下界 <? super T>

  • // super 只能添加 Father 和 Father 的子類,不能添加 Father 的父類,讀取出來的東西只能存放在Object類裏
    List<? super Father> list = new ArrayList<>();
    list.add(new Father());
    list.add(new Human());           // compile error 
    list.add(new Son());
    Father person1 = list.get(0);    // compile error 
    Son son = list.get(0);           // compile error 
    Object object1 = list.get(0);
  • 因爲下界規定了元素的最小粒度的下限,實際上是放鬆了容器元素的類型控制。既然元素是 Father 的基類,那往裏存粒度比 Father 小的都可以。出於對類型安全的考慮,我們可以加入 Father 對象或者其任何子類 (如Son)對象,但由於編譯器並不知道List 的內容究竟是 Father 的哪個超類,因此不允許加入特定的任何超類 (如Human)。而當我們讀取的時候,編譯器在不知道是什麼類型的情況下只能返回 Object 對象,因爲 Object 是任何 Java 類的最終祖先類。但這樣的話,元素的類型信息就全部丟失了。

PECS原則

  • 最後看一下什麼是PECS(Producer Extends Consumer Super)原則,已經很好理解了:

    • 頻繁往外讀取內容的,適合用上界 Extends。
    • 經常往裏插入的,適合用下界 Super。

總結

  • extends 可用於返回類型限定,不能用於參數類型限定(換句話說:? extends xxx 只能用於方法返回類型限定,JDK 能夠確定此類的最小繼承邊界爲xxx,只要是這個類的父類都能接收,但是傳入參數無法確定具體類型,只能接受 null 的傳入)。
  • super 可用於參數類型限定,不能用於返回類型限定(換句話說:? supper xxx 只能用於方法傳參,因爲 JDK 能夠確定傳入爲xxx的子類,返回只能用 Object 類接收)。
  • ? 既不能用於方法參數傳入,也不能用於方法返回。

 

  • 注意:
  • 1、List<Object> o = new ArrayList<Long>();        // 會報錯
    • 不同於數組 Object[] o,Long[] o,因爲 List<Type1> 與 List<Type2> 不互爲子類型或超類型
  • 2、泛型方法的使用
    • 定義泛型方法語法格式如下:
    • 這就是所謂的泛型方法,意思是這個方法所自己定義的泛型,在調用時,根據參數類型確定它的類型。
    • 調用泛型方法語法格式如下:
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章