泛型總結
泛型是什麼?
一句話說就是類型參數化。什麼意思呢?參數化的意思就是咱們在定義的時候不知道具體的值,咱們在到咱們實際運行的時候才知道具體的值。類型參數化就是具體類型在定義的時候不知道,在實際運行的時候是肯定的某一個類型。url
Java 是如何實現泛型的?
泛型是不少高級語言都有的特性。根據定義,泛型在運行時表示同一個類型,咱們比較容易想到 List<A>
和 List<B>
用 2 個不一樣的 Class 表示,這個是可行的,可是 Java 因爲須要兼容支持舊的代碼,並且在推出泛型前就提供了容器類,這種方式(List<A>
和 List<B>
用 2 個不一樣的 Class)沒法兼容之前的老代碼,因此這個實現方法不適用。因此 Java 大佬們想了另一種方式來實現泛型,這種方式就是類型擦除
。.net
什麼是泛型的類型擦除呢?
類型擦除就是在實際生成字節碼的時候,編譯器源碼裏面定義的 List<A>
變成了 List<Object>
,源碼裏面定義的 A Class
被擦除
了,變成了 Object
,同時在使用的時候,會強制類型轉換,把取出來的 object
轉成 A
的實例去使用。這就是類型擦除。code
初步看,泛型擦除好像是沒什麼大的問題,可是仔細想一想,在強制類型轉換的時候,因爲會丟掉類型的一些信息,會致使一些不符合預期的事情。好比有個基類 A,和它的兩個子類 B 和 C ,而後咱們有下面的一段代碼。get
List<A> listA = new ArrayList<A>(); listA.add(new B()); // 錯誤的,
第二行代碼是不符合預期的,由於 listA
裏面指望放的是 A
而不是 B
。 可是這個好像不太符合預期,咱們有時候但願子類是能夠放進容器裏面的。可是若是支持這個操做的話,會發生什麼呢?取出來來的是 B
仍是 C
?若是不能明確,那麼就沒有實現「泛型」。編譯器
爲了解決這個問題, Java 大佬們想了個方法,提出了一些通配符來解決這些問題。源碼
泛型的通配符 ?
、extends
和 super
在理解通配符以前,咱們須要知道的是,通配符的發明是爲了解決什麼問題?至少要解決的一個問題是:容器裏面放進去的是什麼,取出來的就是什麼。io
這個問題,其實分兩步,放進去,是說放進去同一種類型的東西。取出來,是說取出同一種類型的東西。或者說,用到通配符的地方應該是在不一樣的地方,一個地方把數據寫到容器,另一個地方把數據從容器拿出來,若是實在同一個代碼塊裏寫入和讀取數據到同一個容器,應該是知道具體類型的,是不須要用到通配符的。編譯
?
通配符
?
通配符稱爲無限通配符,表示不肯定或者不關心類型。class
extends 通配符
通常稱爲上界通配符,表示的意思是:取值範圍爲 (某個類的子類, 某個類]。再想一想咱們以前說的,通配符要解決的問題?放進去的是什麼,取出來的就應該是什麼。放數據和取數據應用在不一樣的場景。若是咱們在同一個場景,就不須要用到通配符了,由於類型是已知的。容器
經過上面的表述,容易推斷出來 <? extends E>
的集合只能往外拿數據,由於取出來的必定是 E
,可是放進去的不知道是什麼,多是 E
,也多是 E
的子類,若是容許往集合裏面放東西,就不能保證放進去的是什麼,拿出來的就是什麼了。由於只能保證拿出來的是 E
。
這個特性也叫作協變。
super 通配符
通常稱爲下界通配符,表示的意思是:取值範圍爲 [某個類,這個類的父類)。結合上面小節的解釋,能夠推斷出 <? super S>
的集合只能往裏面放數據,而不能從裏面拿東西,爲何呢?由於 <? extends E>
解決的就是拿出來的問題啊,因此這個解決的就是放進去的問題啊,囧。裏面放的是下限或者下限的子類。
這個特性也叫作逆變。
小結
通配符與一個規則, PE-CS
。
-
PE
簡單的說,當只想從集合中獲取元素,請把這個集合當作生產者,請使用<? extends T>,這就是 Producer extends 原則,PECS原則中的PE部分。集合生產元素後,就能夠拿過來用了。 -
CS
簡單的說,當你僅僅想增長元素到集合,把這個集合當作消費者,請使用<? super T>。這就是 Consumer super 原則,PECS原則中的CS部分。集合消費元素,這樣就能夠往裏面放了 -
同時做爲生產者和消費者的狀況不存在,由於你能夠指定具體的泛型。
參考資料: