天天看點

泛型 java extends_Java泛型中extends和super的了解

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

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

extends >: 是指“下界通配符(Lower Bounds Wildcards)”

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

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

class Fruit { }

class Apple extends Fruit { }

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

class Plate {

private T item;

public Plate(T t) {

item = t;

}

public void set(T t) {

item = t;

}

public T get() {

return item;

}

}

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

Plate p = new Plate(new Apple());

但實際上Java編譯器不允許這個操作,會報錯。

Type mismatch: cannot convert from Plate to Plate

實際上,編譯器認定的邏輯是這樣的:

蘋果is-a水果

裝蘋果的盤子not-is-a裝水果的盤子

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

為了讓泛型用起來更舒服,于是就有了 extends T>和< super T>的辦法,來讓“水果盤子”和“蘋果盤子”有聯系。

2 什麼是上界

下面代碼就是“上界通配符”:

Plate extends Fruit>

意思是:一個能放水果以及一切是水果派生類的盤子。這和我們人類的邏輯就比較接近了。Plate extends Fruit>和Plate最大的差別就是:Plate extends Fruit>是Plate以及Plate的基類。直接的好處就是,我們可以用“蘋果盤子”給“水果盤子”指派。

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

如果把Fruit和Apple的例子再擴充一下,食物分成水果和肉類,水果還有蘋果和香蕉,肉類有豬肉和牛肉,蘋果還有兩種青蘋果和紅蘋果。

在這個繼承體系中,下界通配符Plate extends Fruit> 覆寫下圖中藍色的區域。

泛型 java extends_Java泛型中extends和super的了解

繼承體系圖

3. 什麼是下界

相應的,“下界通配符”:

Plate super Fruit>

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

泛型 java extends_Java泛型中extends和super的了解

繼承體系圖

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

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

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

class Plate {

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

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

//不能存入任何元素

p.set(new Fruit());//Error

p.set(new Apple());//Error

//讀取出來的東西隻能存放在Fruit或它的基類裡

Fruit other1 = p.get();

Object other2 = p.get();

Apple other3 = 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 List fill(T... t);

但通配符>沒有這種限制,Plate>單純的就表示:盤子裡放了一個東西,是什麼我不知道。

是以Plate裡什麼都放不進去。

4.2 下界 super T>不影響往裡存,但往外取隻能放在Object對象裡

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

//存入元素正常

p.set(new Fruit());

p.set(new Apple());

//讀取出來的東西隻能存放在Object類裡

Fruit other1 = p.get();//Error

Apple other2 = p.get();//Error

Object other3 = p.get();

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

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

5. PECS原則

1) T super B>

對于這個泛型,?代表容器裡的元素類型,由于隻規定了元素必須是B的超類,導緻元素沒有明确統一的“根”(除了Object這個必然的根),是以這個泛型你其實無法使用它,對吧,除了把元素強制轉成Object。是以,對把參數寫成這樣形态的函數,你函數體内,隻能對這個泛型做插入操作,而無法讀

2) T extends B>

由于指定了B為所有元素的“根”,你任何時候都可以安全的用B來使用容器裡的元素,但是插入有問題,由于供奉B為祖先的子樹有很多,不同子樹并不相容,由于實參可能來自于任何一顆子樹,是以你的插入很可能破壞函數實參,是以,對這種寫法的形參,禁止做插入操作,隻做讀取

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

頻繁往外讀取内容的,适合用上界Extends。

經常往裡插入的,适合用下界Super。