溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Java中super與extends有什么區(qū)別

發(fā)布時(shí)間:2021-06-25 11:25:49 來源:億速云 閱讀:126 作者:chen 欄目:大數(shù)據(jù)

本篇內(nèi)容主要講解“Java中super與extends有什么區(qū)別”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“Java中super與extends有什么區(qū)別”吧!

// compile error
// List <? extends Fruit> appList2 = new ArrayList();
// appList2.add(new Fruit());
// appList2.add(new Apple());
// appList2.add(new RedApple());

List <? super Fruit> appList = new ArrayList();
appList.add(new Fruit());
appList.add(new Apple());
appList.add(new RedApple());

-------------------------------------------------------------------------------------------------

Java中super與extends有什么區(qū)別

胖君

he who transplants sustains

1,200 人贊同了該回答

題主說的<? extends T>和<? super T>是Java泛型中的“通配符(Wildcards)”“邊界(Bounds)”的概念。

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

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

1. 為什么要用通配符和邊界?

使用泛型的過程中,經(jīng)常出現(xiàn)一種很別扭的情況。比如按照題主的例子,我們有Fruit類,和它的派生類Apple類。

class Fruit {}
class Apple extends Fruit {}


然后有一個(gè)最簡單的容器:Plate類。盤子里可以放一個(gè)泛型的“東西”。我們可以對(duì)這個(gè)東西做最簡單的“”和“”的動(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> p=new Plate<Apple>(new Apple());


但實(shí)際上Java編譯器不允許這個(gè)操作。會(huì)報(bào)錯(cuò),“裝蘋果的盤子”無法轉(zhuǎn)換成“裝水果的盤子”。

error: incompatible types: Plate<Apple> cannot be converted to Plate<Fruit>


所以我的尷尬癥就犯了。實(shí)際上,編譯器腦袋里認(rèn)定的邏輯是這樣的:

  • 蘋果 IS-A 水果

  • 裝蘋果的盤子 NOT-IS-A 裝水果的盤子

所以,就算容器里裝的東西之間有繼承關(guān)系,但容器之間是沒有繼承關(guān)系的。所以我們不可以把Plate<Apple>的引用傳遞給Plate<Fruit>。

為了讓泛型用起來更舒服,Sun的大腦袋們就想出了<? extends T>和<? super T>的辦法,來讓”水果盤子“和”蘋果盤子“之間發(fā)生關(guān)系。

2. 什么是上界?

下面代碼就是“上界通配符(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ū)域。

Java中super與extends有什么區(qū)別

3. 什么是下界?

相對(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中super與extends有什么區(qū)別

4. 上下界通配符的副作用

邊界讓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;}
}
4.1 上界<? extends T>不能往里存,只能往外取

<? extends Fruit>會(huì)使往盤子里放東西的set( )方法失效。但取東西get( )方法還有效。比如下面例子里兩個(gè)set()方法,插入Apple和Fruit都報(bào)錯(cuò)。

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

原因是編譯器只知道容器內(nèi)是Fruit或者它的派生類,但具體是什么類型不知道??赡苁荈ruit?可能是Apple?也可能是Banana,RedApple,GreenApple?編譯器在看到后面用Plate<Apple>賦值以后,盤子里沒有被標(biāo)上有“蘋果”。而是標(biāo)上一個(gè)占位符:CAP#1,來表示捕獲一個(gè)Fruit或Fruit的子類,具體是什么類不知道,代號(hào)CAP#1。然后無論是想往里插入Apple或者M(jìn)eat或者Fruit編譯器都不知道能不能和這個(gè)CAP#1匹配,所以就都不允許。

所以通配符<?>和類型參數(shù)<T>的區(qū)別就在于,對(duì)編譯器來說所有的T都代表同一種類型。比如下面這個(gè)泛型方法里,三個(gè)T都指代同一個(gè)類型,要么都是String,要么都是Integer。

public <T> List<T> fill(T... t);

但通配符<?>沒有這種約束,Plate<?>單純的就表示:盤子里放了一個(gè)東西,是什么我不知道。

所以題主問題里的錯(cuò)誤就在這里,Plate<? extends Fruit>里什么都放不進(jìn)去。

4.2 下界<? super T>不影響往里存,但往外取只能放在Object對(duì)象里

使用下界<? super Fruit>會(huì)使從盤子里取東西的get( )方法部分失效,只能存放到Object對(duì)象里。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();

因?yàn)橄陆缫?guī)定了元素的最小粒度的下限,實(shí)際上是放松了容器元素的類型控制。既然元素是Fruit的基類,那往里存粒度比Fruit小的都可以。但往外讀取元素就費(fèi)勁了,只有所有類的基類Object對(duì)象才能裝下。但這樣的話,元素的類型信息就全部丟失。

5. PECS原則

最后看一下什么是PECS(Producer Extends Consumer Super)原則,已經(jīng)很好理解了:

  1. 頻繁往外讀取內(nèi)容的,適合用上界Extends。

  2. 經(jīng)常往里插入的,適合用下界Super。

到此,相信大家對(duì)“Java中super與extends有什么區(qū)別”有了更深的了解,不妨來實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI