Java中 ? extends T 和 ? super T的理解
? 通配符類型
- <? extends T> 表示類型的上界,表示參數化類型的可能是T 或是 T的子類;
- <? super T> 表示類型下界(Java Core中叫超類型限定),表示參數化類型是此類型的超類型(父類型),直至Object;
上界<? extends T>不能往裡存,隻能往外取
比如,我們現在定義:List<? extends T>首先你很容易誤解它為繼承於T的所有類的集合,你可能認為,你定義的這個List可以用來put任何T的子類,那麼我們看下面的代碼:
import java.util.LinkedList; import java.util.List; public class test { public static void main(String[] args) { List<? extends Father> list = new LinkedList<>(); list.add(new Son()); } } class Human{ } class Father extends Human{ } class Son extends Father{ } class LeiFeng extends Father { }
list.add(new Son());這行會報錯:The method put(Son) is undefined for the type List<capture#1-of ? extends Father>
List<? extends Father> 表示 “具有任何從Son繼承類型的列表”,編譯器無法確定List所持有的類型,所以無法安全的向其中添加對象。可以添加null,因為null 可以表示任何類型。所以List 的add 方法不能添加任何有意義的元素,但是可以接受現有的子類型List 賦值。
你也許試圖這樣做:
List<? extends Father> list = new LinkedList<Son>(); list.add(new Son());
即使你指明瞭為Son類型,也不能用add方法添加一個Son對象。
list中為什麼不能加入Father類和Father類的子類呢,我們來分析下。
List<? extends Father>表示上限是Father,下面這樣的賦值都是合法的
List<? extends Father> list1 = new ArrayList<Father>(); List<? extends Father> list2 = new ArrayList<Son>(); List<? extends Father> list3 = new ArrayList<LeiFeng>();
如果List<? extends Father>支持add方法的話:
- 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,XiaoMing?編譯器在看到後面用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<? extends Father>裡什麼都放不進去。
List<? extends Father> list不能進行add,但是,這種形式還是很有用的,雖然不能使用add方法,但是可以在初始化的時候一個Season指定不同的類型。比如:
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>不影響往裡存,但往外取隻能放在Object對象裡
下界用super進行聲明,表示參數化的類型可能是所指定的類型,或者是此類型的父類型,直至Object。
//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類接收)。
- ? 既不能用於方法參數傳入,也不能用於方法返回。
帶有super超類型限定的通配符可以向泛型對象中寫入,帶有extends子類型限定的通配符可以向泛型對象讀取。
到此這篇關於Java中 ? extends T 和 ? super T的理解的文章就介紹到這瞭,更多相關Java中 ? extends T 和 ? super T內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!