困擾多年的Java泛型 extends T> super T>,終於搞清楚了!

1、爲何要用通配符和邊界?分佈式

使用泛型的過程當中,常常出現一種很彆扭的狀況。微服務

好比咱們有Fruit類,和它的派生類Apple源碼分析

困擾多年的Java泛型 extends T>  super T>,終於搞清楚了!

 

 

而後有一個最簡單的容器:Plate性能

盤子裏能夠放一個泛型的」東西學習

咱們能夠對這個東西作最簡單的「」和「」的動做:set( )get( )方法。ui

困擾多年的Java泛型 extends T>  super T>,終於搞清楚了!

 

 

現定義一個「水果盤」,邏輯上水果盤固然能夠裝蘋果。視頻

 

困擾多年的Java泛型 extends T>  super T>,終於搞清楚了!

 

 

但實際上Java編譯器不容許這個操做。會報錯,「裝蘋果的盤子」沒法轉換成「裝水果的盤子」。對象

 

困擾多年的Java泛型 extends T>  super T>,終於搞清楚了!

 

 

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

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

因此,就算容器裏裝的東西之間有繼承關係,但容器之間是沒有繼承關係。繼承

因此咱們不能夠把Plate<Apple>的引用傳遞給Plate<Fruit>。

爲了讓泛型用起來更舒服,Sun的大師們就想出了<? extends T>和<? super T>的辦法,來讓」水果盤子「和」蘋果盤子「之間發生正當關係。

2、上界

下面就是上界通配符(Upper Bounds Wildcards)

 

困擾多年的Java泛型 extends T>  super T>,終於搞清楚了!

 

 

一個能放水果以及一切是水果派生類的盤子

再直白點就是:啥水果都能放的盤子

這和咱們人類的邏輯就比較接近了

Plate<? extends Fruit>和Plate<Apple>最大的區別就是:Plate<? extends Fruit>是Plate<Fruit>及Plate<Apple>的基類

直接的好處就是,咱們能夠用「蘋果盤」給「水果盤」賦值了。

 

困擾多年的Java泛型 extends T>  super T>,終於搞清楚了!

 

 

再擴展一下,食物分紅水果和肉類,水果有蘋果和香蕉,肉類有豬肉和牛肉,蘋果還有兩種青蘋果和紅蘋果。

困擾多年的Java泛型 extends T>  super T>,終於搞清楚了!

 

 

在這個體系中,上界通配符Plate<? extends Fruit>覆蓋下圖中藍色的區域。

困擾多年的Java泛型 extends T>  super T>,終於搞清楚了!

 

 

3、下界

相對應的下界通配符(Lower Bounds Wildcards)

 

困擾多年的Java泛型 extends T>  super T>,終於搞清楚了!

 

 

表達的就是相反的概念:一個能放水果以及一切是水果基類的盤子。

Plate<? super Fruit>是Plate<Fruit>的基類,但不是Plate<Apple>的基類

對應剛纔那個例子,Plate<? super Fruit>覆蓋下圖中紅色的區域。

困擾多年的Java泛型 extends T>  super T>,終於搞清楚了!

 

 

4、上下界通配符的反作用

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

仍是以剛纔的Plate爲例。咱們能夠對盤子作兩件事,往盤子裏set( )新東西,以及從盤子裏get( )東西。

困擾多年的Java泛型 extends T>  super T>,終於搞清楚了!

 

 

一、上界<? extends T>不能往裏存,只能往外取

<? extends Fruit>會使往盤子裏放東西的set( )方法失效

但取東西get( )方法還有效

好比下面例子裏兩個set()方法,插入Apple和Fruit都報錯。

困擾多年的Java泛型 extends T>  super T>,終於搞清楚了!

 

 

編譯器只知道容器內是Fruit或者它的派生類,但具體是什麼類型不知道。

多是Fruit?多是Apple?也多是Banana,RedApple,GreenApple?編譯器在看到後面用Plate<Apple>賦值之後,盤子裏沒有被標上有「蘋果」。而是標上一個佔位符:capture#1,來表示捕獲一個Fruit或Fruit的子類,具體是什麼類不知道,代號capture#1。

而後不管是想往裏插入Apple或者Meat或者Fruit編譯器都不知道能不能和這個capture#1匹配,因此就都不容許。

因此通配符<?>和類型參數<T>的區別就在於,對編譯器來講全部的T都表明同一種類型。

好比下面這個泛型方法裏,三個T都指代同一個類型,要麼都是String,要麼都是Integer...

 

困擾多年的Java泛型 extends T>  super T>,終於搞清楚了!

 

 

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

二、下界<? super T>不影響往裏存,但往外取只能放在Object對象裏

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

困擾多年的Java泛型 extends T>  super T>,終於搞清楚了!

 

 

由於下界規定了元素的最小粒度的下限,其實是放鬆了容器元素的類型控制。

既然元素是Fruit的基類,那往裏存粒度比Fruit小的均可以。

但往外讀取元素就費勁了,只有全部類的基類Object對象才能裝下。但這樣的話,元素的類型信息就所有丟失。

5、PECS原則

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

一、頻繁往外讀取內容的,適合用上界Extends

二、常常往裏插入的,適合用下界Super

若是想學習Java工程化、高性能及分佈式、深刻淺出。微服務、Spring,MyBatis,Netty源碼分析的朋友能夠加個人Java高級交流:787707172,羣裏有阿里大牛直播講解技術,以及Java大型互聯網技術的視頻免費分享給你們。

相關文章
相關標籤/搜索