Java泛型extends及super區(qū)別實(shí)例解析
<? extends T>和<? super T>是Java泛型中的“通配符(Wildcards)”和“邊界(Bounds)”的概念。
- <? extends T>:是指“上界通配符(Upper Bounds Wildcards)”
- <? super T>:是指“下界通配符(Lower Bounds Wildcards)”
為什么要用通配符和邊界?
使用泛型的過(guò)程中,經(jīng)常出現(xiàn)一種很別扭的情況。比如按照題主的例子,我們有Fruit類,和它的派生類Apple類。
class Fruit {}
class Apple extends Fruit {}
然后有一個(gè)最簡(jiǎn)單的容器:Plate類。盤子里可以放一個(gè)泛型的“東西”。我們可以對(duì)這個(gè)東西做最簡(jiǎn)單的“放”和“取”的動(dòng)作: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;} }
現(xiàn)在我定義一個(gè)“水果盤子”,邏輯上水果盤子當(dāng)然可以裝蘋果:
Plate<Fruit> pfruit=new Plate<Apple>(new Apple());
但實(shí)際上Java編譯器不允許這個(gè)操作。會(huì)報(bào)錯(cuò),“裝蘋果的盤子”無(wú)法轉(zhuǎn)換成“裝水果的盤子”。
error: incompatible types: Plate<Apple> cannot be converted to Plate<Fruit>
所以問(wèn)題就來(lái)了。實(shí)際上,編譯器腦袋里認(rèn)定的邏輯是這樣的:
- 蘋果 IS-A 水果
- 裝蘋果的盤子 NOT-IS-A 裝水果的盤子
所以,就算容器里裝的東西之間有繼承關(guān)系,但容器之間是沒(méi)有繼承關(guān)系的。所以我們不可以把Plate的引用傳遞給Plate。
為了讓泛型用起來(lái)更舒服,Sun的大腦袋們就想出了<? extends T>和<? super T>的辦法,來(lái)讓”水果盤子“和”蘋果盤子“之間發(fā)生關(guān)系。
什么是上界?
下面代碼就是“上界通配符(Upper Bounds Wildcards)”:
Plate<? extends Fruit>
翻譯一下就是:一個(gè)能放水果以及一切是水果派生類的盤子。再直白點(diǎn)就是:啥水果都能放的盤子。這和我們?nèi)祟惖倪壿嬀捅容^接近了。Plate<? extends Fruit>和Plate<Apple>最大的區(qū)別就是:Plate<? extends Fruit>是Plate<Fruit>以及Plate<Apple>的基類。直接的好處就是,我們可以用“蘋果盤子”給“水果盤子”賦值了。
Plate<? extends Fruit> p=new Plate<Apple>(new Apple());
如果把Fruit和Apple的例子再擴(kuò)展一下,食物分成水果和肉類,水果有蘋果和香蕉,肉類有豬肉和牛肉,蘋果還有兩種青蘋果和紅蘋果。
//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{}
在這個(gè)體系中,下界通配符Plate<? extends Fruit>覆蓋下圖中藍(lán)色的區(qū)域。
什么是下界?
相對(duì)應(yīng)的,“下界通配符(Lower Bounds Wildcards)”:
Plate<? super Fruit>
表達(dá)的就是相反的概念:一個(gè)能放水果以及一切是水果基類的盤子。Plate<? super Fruit>是Plate<Fruit>的基類,但不是Plate<Apple>的基類。對(duì)應(yīng)剛才那個(gè)例子,Plate<? super Fruit>覆蓋下圖中紅色的區(qū)域。
上下界通配符的副作用
邊界讓Java不同泛型之間的轉(zhuǎn)換更容易了。但不要忘記,這樣的轉(zhuǎn)換也有一定的副作用。那就是容器的部分功能可能失效。
還是以剛才的Plate為例。我們可以對(duì)盤子做兩件事,往盤子里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>會(huì)使往盤子里放東西的set( )方法失效。但取東西get( )方法還有效。比如下面例子里兩個(gè)set()方法,插入Apple和Fruit都報(bào)錯(cuò):
Plate<? extends Fruit> pfruit=new Plate<Apple>(new Apple()); //不能存入任何元素 pfruit.set(new Fruit()); //Error pfruit.set(new Apple()); //Error //讀取出來(lái)的東西只能存放在Fruit或它的基類里。 Fruit newFruit1=pfruit.get(); Object newFruit2=pfruit.get(); Apple newFruit3=pfruit.get(); //Error
原因是編譯器只知道容器內(nèi)是Fruit或者它的派生類,但具體是什么類型不知道??赡苁荈ruit?可能是Apple?也可能是Banana,RedApple,GreenApple?編譯器在看到后面用Plate賦值以后,盤子里沒(méi)有被標(biāo)上有“蘋果”。而是標(biāo)上一個(gè)占位符:CAP#1,來(lái)表示捕獲一個(gè)Fruit或Fruit的子類,具體是什么類不知道,代號(hào)CAP#1。然后無(wú)論是想往里插入Apple或者M(jìn)eat或者Fruit編譯器都不知道能不能和這個(gè)CAP#1匹配,所以就都不允許。
所以通配符<?>和類型參數(shù)的區(qū)別就在于:
對(duì)編譯器來(lái)說(shuō)所有的T都代表同一種類型。比如下面這個(gè)泛型方法里,三個(gè)T都指代同一個(gè)類型,要么都是String,要么都是Integer。
public <T> List<T> fill(T... t);
但通配符<?>沒(méi)有這種約束,Plate<?>單純的就表示:盤子里放了一個(gè)東西,是什么我不知道。
因此為了安全起見(jiàn),Plate<? extends Fruit>里什么都放不進(jìn)去。
下界<? super T>不影響往里存,但往外取只能放在Object對(duì)象里
使用下界<? super Fruit>會(huì)使從盤子里取東西的get( )方法部分失效,只能存放到Object對(duì)象里。set( )方法正常。
Plate<? super Fruit> pfruit=new Plate<Fruit>(new Fruit()); //存入元素正常 pfruit.set(new Fruit()); pfruit.set(new Apple()); //讀取出來(lái)的東西只能存放在Object類里。 Apple newFruit3=pfruit.get(); //Error Fruit newFruit1=pfruit.get(); //Error Object newFruit2=pfruit.get();
因?yàn)橄陆缫?guī)定了元素的最小粒度的下限,實(shí)際上是放松了容器元素的類型控制。既然元素是Fruit的基類,那往里存粒度比Fruit小的都可以。但往外讀取元素就費(fèi)勁了,只有所有類的基類Object對(duì)象才能裝下。但這樣的話,元素的類型信息就全部丟失。
PECS原則
最后看一下什么是PECS(Producer Extends Consumer Super)原則,已經(jīng)很好理解了:
- 頻繁往外讀取內(nèi)容的,適合用上界Extends。
- 經(jīng)常往里插入的,適合用下界Super。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Java開(kāi)發(fā)完整短信驗(yàn)證碼功能的全過(guò)程
利用短信驗(yàn)證碼進(jìn)行身份驗(yàn)證是目前互聯(lián)網(wǎng)眾多產(chǎn)品常用的一種方式,那么這種短信驗(yàn)證功能是如何實(shí)現(xiàn)的呢,下面這篇文章主要給大家介紹了關(guān)于Java開(kāi)發(fā)完整短信驗(yàn)證碼功能的相關(guān)資料,需要的朋友可以參考下2021-10-10Mybatis?Mapper中多參數(shù)方法不使用@param注解報(bào)錯(cuò)的解決
這篇文章主要介紹了Mybatis?Mapper中多參數(shù)方法不使用@param注解報(bào)錯(cuò)的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。2022-01-01Java多線程常見(jiàn)案例分析線程池與單例模式及阻塞隊(duì)列
這篇文章主要介紹了多線程的常見(jiàn)案例,線程池(重點(diǎn))、單例模式、阻塞隊(duì)列,本文通過(guò)圖文實(shí)例相結(jié)合給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-05-05mybatis實(shí)現(xiàn)增刪改查_(kāi)動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
本文通過(guò)實(shí)例代碼給大家介紹了mybatis實(shí)現(xiàn)增刪改查功能,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧2017-09-09Java多線程編程中synchronized關(guān)鍵字的基礎(chǔ)用法講解
Java的synchronized關(guān)鍵字用于修飾線程同步,用以線程資源共享的目的等,下面就帶來(lái)簡(jiǎn)單的Java多線程編程中synchronized關(guān)鍵字的基礎(chǔ)用法講解2016-06-06Java報(bào)錯(cuò):UnsupportedOperationException in Collection
在Java編程中,UnsupportedOperationException是一種常見(jiàn)的運(yùn)行時(shí)異常,通常在試圖對(duì)不支持的操作執(zhí)行修改時(shí)發(fā)生,它表示當(dāng)前操作不被支持,本文將深入探討UnsupportedOperationException的產(chǎn)生原因,并提供具體的解決方案和最佳實(shí)踐,需要的朋友可以參考下2024-06-06java N皇后實(shí)現(xiàn)問(wèn)題解析
將 n 個(gè)皇后擺放在一個(gè) n x n 的棋盤上,使得每一個(gè)皇后都無(wú)法攻擊到其他皇后,N皇后問(wèn)題是一個(gè)典型的約束求解問(wèn)題,利用遞歸機(jī)制,可以很快的得到結(jié)果,本文將詳細(xì)介紹,需要了解的朋友可以參考下2012-11-11