关于 ? extends T 和 ? super T 的存在意义和使用
問題來源:Java 泛型 <? super T> 中 super 怎么 理解?與 extends 有何不同?
?
<? extends T>和<? super T>是Java泛型中的“通配符(Wildcards)”和“邊界(Bounds)”的概念。
- <? extends T>:是指?“上界通配符(Upper Bounds Wildcards)”
- <? super T>:是指?“下界通配符(Lower Bounds Wildcards)”
為什么要用通配符和邊界?
使用泛型的過程中,經常出現一種很別扭的情況。比如按照題主的例子,我們有Fruit類,和它的派生類Apple類。
class Fruit {} class Apple extends Fruit {}然后有一個最簡單的容器:Plate類。盤子里可以放一個泛型的“東西”。我們可以對這個東西做最簡單的“放”和“取”的動作:set( )和get( )方法。
class Plate<T>{private T item;public Plate(T t){item=t;}public void set(T t){item=t;}public T get(){return item;} }現在我定義一個“水果盤子”,邏輯上水果盤子當然可以裝蘋果。
Plate<Fruit> p=new Plate<Apple>(new Apple());但實際上Java編譯器不允許這個操作。會報錯,“裝蘋果的盤子”無法轉換成“裝水果的盤子”。
error: incompatible types: Plate<Apple> cannot be converted to Plate<Fruit>所以我的尷尬癥就犯了。實際上,編譯器腦袋里認定的邏輯是這樣的:
- 蘋果 IS-A 水果
- 裝蘋果的盤子 NOT-IS-A 裝水果的盤子
所以,就算容器里裝的東西之間有繼承關系,但容器之間是沒有繼承關系的。所以我們不可以把Plate的引用傳遞給Plate。
為了讓泛型用起來更舒服,Sun的大腦袋們就想出了<? extends T>和<? super T>的辦法,來讓”水果盤子“和”蘋果盤子“之間發生關系。
什么是上界?
下面代碼就是“上界通配符(Upper Bounds Wildcards)”:
Plate<? extends Fruit>翻譯成人話就是:一個能放水果以及一切是水果派生類的盤子。再直白點就是:啥水果都能放的盤子。這和我們人類的邏輯就比較接近了。Plate<? extends Fruit>和Plate<Apple>最大的區別就是:Plate<? extends Fruit>是Plate<Fruit>以及Plate<Apple>的基類。直接的好處就是,我們可以用“蘋果盤子”給“水果盤子”賦值了。
Plate<? extends Fruit> p=new Plate<Apple>(new Apple());如果把Fruit和Apple的例子再擴展一下,食物分成水果和肉類,水果有蘋果和香蕉,肉類有豬肉和牛肉,蘋果還有兩種青蘋果和紅蘋果。
?
//Lev 1 class Food{}//Lev 2 class Fruit extends Food{} class Meat extends Food{}//Lev 3 class Apple extends Fruit{} class Banana extends Fruit{} class Pork extends Meat{} class Beef extends Meat{}//Lev 4 class RedApple extends Apple{} class GreenApple extends Apple{}?
在這個體系中,上界通配符?Plate<? extends Fruit>?覆蓋下圖中藍色的區域。
?
什么是下界?
相對應的,“下界通配符(Lower Bounds Wildcards)”:
Plate<? super Fruit>表達的就是相反的概念:一個能放水果以及一切是水果基類的盤子。Plate<? super Fruit>是Plate<Fruit>的基類,但不是Plate<Apple>的基類。對應剛才那個例子,Plate<? super Fruit>覆蓋下圖中紅色的區域。
?
上下界通配符的副作用
邊界讓Java不同泛型之間的轉換更容易了。但不要忘記,這樣的轉換也有一定的副作用。那就是容器的部分功能可能失效。
還是以剛才的Plate為例。我們可以對盤子做兩件事,往盤子里set()新東西,以及從盤子里get()東西。
class Plate<T>{private T item;public Plate(T t){item=t;}public void set(T t){item=t;}public T get(){return item;} }上界<? extends T>不能往里存,只能往外取
<? extends Fruit>會使往盤子里放東西的set( )方法失效。但取東西get( )方法還有效。比如下面例子里兩個set()方法,插入Apple和Fruit都報錯。
?
Plate<? extends Fruit> p=new Plate<Apple>(new Apple());//不能存入任何元素 p.set(new Fruit()); //Error p.set(new Apple()); //Error//讀取出來的東西只能存放在Fruit或它的基類里。 Fruit newFruit1=p.get(); Object newFruit2=p.get(); Apple newFruit3=p.get(); //Error?
原因是編譯器只知道容器內是Fruit或者它的派生類,但具體是什么類型不知道。可能是Fruit?可能是Apple?也可能是Banana,RedApple,GreenApple?編譯器在看到后面用Plate賦值以后,盤子里沒有被標上有“蘋果”。而是標上一個占位符:CAP#1,來表示捕獲一個Fruit或Fruit的子類,具體是什么類不知道,代號CAP#1。然后無論是想往里插入Apple或者Meat或者Fruit編譯器都不知道能不能和這個CAP#1匹配,所以就都不允許。
所以通配符<?>和類型參數的區別就在于,對編譯器來說所有的T都代表同一種類型。比如下面這個泛型方法里,三個T都指代同一個類型,要么都是String,要么都是Integer。
public <T> List<T> fill(T... t);但通配符<?>沒有這種約束,Plate<?>單純的就表示:盤子里放了一個東西,是什么我不知道。
所以題主問題里的錯誤就在這里,Plate<? extends Fruit>里什么都放不進去。
下界<? super T>不影響往里存,但往外取只能放在Object對象里
使用下界<? super Fruit>會使從盤子里取東西的get( )方法部分失效,只能存放到Object對象里。set( )方法正常。
?
Plate<? super Fruit> p=new Plate<Fruit>(new Fruit());//存入元素正常 p.set(new Fruit()); p.set(new Apple());//讀取出來的東西只能存放在Object類里。 Apple newFruit3=p.get(); //Error Fruit newFruit1=p.get(); //Error Object newFruit2=p.get();?
因為下界規定了元素的最小粒度的下限,實際上是放松了容器元素的類型控制。既然元素是Fruit的基類,那往里存粒度比Fruit小的都可以。但往外讀取元素就費勁了,只有所有類的基類Object對象才能裝下。但這樣的話,元素的類型信息就全部丟失。
PECS原則
最后看一下什么是PECS(Producer Extends Consumer Super)原則,已經很好理解了:
- 頻繁往外讀取內容的,適合用上界Extends。
- 經常往里插入的,適合用下界Super。
?
?
來自知乎的另一個回答:
鏈接:https://www.zhihu.com/question/20400700/answer/117624335
java是單繼承,所有繼承的類構成一棵樹。
假設A和B都在一顆繼承樹里(否則super,extend這些詞沒意義)。
A super B 表示A是B的父類或者祖先,在B的上面。
A extend B 表示A是B的子類或者子孫,在B下面。
由于樹這個結構上下是不對稱的,所以這兩種表達區別很大。假設有兩個泛型寫在了函數定義里,作為函數形參(形參和實參有區別):
1) 參數寫成:T<? super B>,對于這個泛型,?代表容器里的元素類型,由于只規定了元素必須是B的超類,導致元素沒有明確統一的“根”(除了Object這個必然的根),所以這個泛型你其實無法使用它,對吧,除了把元素強制轉成Object。所以,對把參數寫成這樣形態的函數,你函數體內,只能對這個泛型做插入操作,而無法讀
2) 參數寫成: T<? extends B>,由于指定了B為所有元素的“根”,你任何時候都可以安全的用B來使用容器里的元素,但是插入有問題,由于供奉B為祖先的子樹有很多,不同子樹并不兼容,由于實參可能來自于任何一顆子樹,所以你的插入很可能破壞函數實參,所以,對這種寫法的形參,禁止做插入操作,只做讀取
具體請看 《effective java》里,Joshua Bloch提出的PECS原則
java - What is PECS (Producer Extends Consumer Super)?
總結
以上是生活随笔為你收集整理的关于 ? extends T 和 ? super T 的存在意义和使用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: html 当当网页面,静态网页当当网(H
- 下一篇: GitModel|Task04|随机模拟