1. 程式人生 > 其它 ><? extends T> 和 <? super T> 的使用

<? extends T> 和 <? super T> 的使用

阿里規範中有一個

12.【強制】泛型萬用字元<? extends T>來接收返回的資料,此寫法的泛型集合不能使用 add 方法, 而<? super T>不能使用 get 方法,兩者在介面呼叫賦值的場景中容易出錯。

具體的分析可以看下如下 :

最終PECS (Producer Extends Consumer Super )原則

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

<? extends T> 和 <? super T> 是Java泛型中的“萬用字元(Wildcards)” 和 “邊界(Bounds)”的概念

  • <? extends T> 是指 “上界萬用字元(Upper Bounds Wildcards)
  • <? super T> 是指 “下界萬用字元(Lower Bounds Wildcards)

1. 為什麼要用萬用字元和邊界?

使用泛型的過程中,經常出現一種很彆扭的情況。比如,我們有Fruit類,和它的派生類Apple類。

class Fruit {}
class Apple extends Fruit {}

然後有一個最簡單的容器:Plate類。盤子裡可以放一個泛型的“東西”。我們可以對這個東西做最簡單的“放”和“取”的動作:set()和get()方法。

public class Plate<T> {
    private T item;

    public Plate(T t) {
        this.item = t;
    }

    public T get() {
        return item;
    }

    public void set(T item) {
        this.item = item;
    }

}

現在我定義一個“水果盤子”,邏輯上水果盤子當然可以裝蘋果。

Plate<Fruit> p=new Plate<Apple>(new Apple());

但實際上Java編譯器不允許這個操作。會報錯,“裝蘋果的盤子”無法轉換成“裝水果的盤子”。

Type mismatch: cannot convert from Plate<Apple> to Plate<Fruit>

所以我的尷尬症就犯了。實際上,編譯器腦袋裡認定的邏輯是這樣的:

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

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

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

2. 什麼是上界?

下面程式碼就是“上界萬用字元(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 ReadApple extends Apple{}
class GreenApple extends Apple{}

在這個體系中,上界萬用字元 “Plate<? extends Fruit>” 覆蓋下圖中藍色的區域

3. 什麼是下界?

相對應的,下界萬用字元(Lower Bounds Wildcards)”

Plate<? super Fruit>

表達的就是相反的概念:一個能放水果以及一切是水果基類的盤子。Plate<? super Fruit>是Plate<Fruit>的基類,但不是Plate<Apple>的基類。對應剛才那個例子,Plate<? super Fruit>覆蓋下圖中紅色的區域。

4. 上下界萬用字元的副作用

邊界讓Java不同泛型之間的轉換更容易了。但不要忘記,這樣的轉換也有一定的副作用。那就是容器的部分功能可能失效

還是以剛才的Plate為例。我們可以對盤子做兩件事,往盤子裡set( )新東西,以及從盤子裡get( )東西。

public class Plate<T> {
    private T item;

    public Plate(T t) {
        this.item = t;
    }

    public T get() {
        return item;
    }

    public void set(T item) {
        this.item = item;
    }
}

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

<? extends Fruit>會使往盤子裡放東西的set()方法失效。但取東西get()方法還有效。比如下面例子裡兩個set()方法,插入Apple和Fruit都報錯。

Plate<? extends Fruit> fruitPlate = new Plate<Apple>(new Apple());

//不能存入任何元素
fruitPlate.set(new Apple());    //Error  編譯錯誤
fruitPlate.set(new Fruit());    //Error  編譯錯誤

//讀取出來的東西只能放在Fruit或它的基類裡
Fruit newFruit1=fruitPlate.get();
Object newFruit2 = fruitPlate.get();
Apple newFruit3 = fruitPlate.get();  //Error

原因是編譯器只知道容器內是Fruit或者它的派生類,但具體是什麼型別不知道。可能是Fruit?可能是Apple?也可能是Banana,RedApple,GreenApple?編譯器在看到後面用Plate<Apple>賦值以後,盤子裡沒有被標上有“蘋果”。而是標上一個佔位符:CAP#1,來表示捕獲一個Fruit或Fruit的子類,具體是什麼類不知道,代號CAP#1。然後無論是想往裡插入Apple或者Meat或者Fruit編譯器都不知道能不能和這個CAP#1匹配,所以就都不允許。

所以萬用字元<?>和型別引數<T>的區別就在於,對編譯器來說所有的T都代表同一種類型。比如下面這個泛型方法裡,三個T都指代同一個型別,要麼都是String,要麼都是Integer。

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

但萬用字元<?>沒有這種約束,Plate<?>單純的就表示:盤子裡放了一個東西,是什麼我不知道。

4.2 下界<? super T>不影響往裡存,但往外取只能放在Object物件裡

使用下界<? super Fruit>會使從盤子裡取東西的get( )方法部分失效,只能存放到Object物件裡。set( )方法正常。

Plate<? super Fruit> fruitPlate = new Plate<Fruit>(new Fruit());

//存入元素正常
fruitPlate.set(new Apple());
fruitPlate.set(new Fruit());

//讀取出來的東西只能存放在Object類裡
Object newFruit1 = fruitPlate.get();
Fruit newFruit2 = fruitPlate.get();  //Error
Apple newFruit3 = fruitPlate.get();  //Error

因為下界規定了元素的最小粒度的下限,實際上是放鬆了容器元素的型別控制。既然元素是Fruit的基類,那往裡存粒度比Fruit小的都可以。但往外讀取元素就費勁了,只有所有類的基類Object物件才能裝下。但這樣的話,元素的型別資訊就全部丟失。

5. PECS原則

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

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

轉自:https://www.cnblogs.com/xd502djj/p/13237402.html