文章目錄
- 泛型總結
-
- 泛型是什麼?
- Java 是如何實作泛型的?
- 什麼是泛型的類型擦除呢?
- 泛型的通配符 `?`、`extends` 和 `super`
-
- ?通配符
- extends 通配符
- super 通配符
- 小結
- 參考資料
泛型總結
泛型是什麼?
簡單說就是類型參數化,什麼意思呢?參數化的意思就是我們在定義的時候不知道具體的值,我們在到我們實際運作的時候才知道具體的值。類型參數化就是具體類型在定義的時候不知道,在實際運作的時候是确定的某一個類型。
Java 是如何實作泛型的?
泛型是很多進階語言都有的特性。根據定義,泛型在運作時表示同一個類型,我們比較容易想到
List<A>
和
List<B>
用 2 個不同的 Class 表示,這個是可行的,但是 Java 由于需要相容支援舊的代碼,而且在推出泛型前就提供了容器類,這種方式(
List<A>
和
List<B>
用 2 個不同的 Class)無法相容以前的老代碼,是以這個實作方法不适用。是以 Java 大佬們想了另外一種方式來實作泛型,這種方式就是
類型擦除
。
什麼是泛型的類型擦除呢?
類型擦除就是在實際生成位元組碼的時候,編譯器源碼裡面定義的
List<A>
變成了
List<Object>
,源碼裡面定義的
A Class
被
擦除
了,變成了
Object
,同時在使用的時候,會強制類型轉換,把取出來的
object
轉成
A
的執行個體去使用。這就是類型擦除。
初步看,泛型擦除好像是沒什麼大的問題,但是仔細想想,在強制類型轉換的時候,由于會丢掉類型的一些資訊,會導緻一些不符合預期的事情。比如有個基類 A,和它的兩個子類 B 和 C ,然後我們有下面的一段代碼。
List<A> listA = new ArrayList<A>();
listA.add(new B()); // 錯誤的,
第二行代碼是不符合預期的,因為 list 裡面期望放的是 A 而不是 B。 但是這個好像不太符合預期,我們有時候希望子類是可以放進容器裡面的。但是如果支援這個操作的話,會發生什麼呢?取出來來的是 B 還是 C ?如果不能明确,那麼就沒有實作“泛型”。
為了解決這個問題, Java 大佬們想了個方法,提出了一些通配符來解決這些問題。
泛型的通配符 ?
、 extends
和 super
?
extends
super
在了解通配符之前,我們需要知道的是,通配符的發明是為了解決什麼問題?至少要解決的一個問題是:容器裡面放進去的是什麼,取出來的就是什麼。
這個問題,其實分兩步,放進去,是說放進去同一種類型的東西。取出來,是說取出同一種類型的東西。或者說,用到通配符的地方應該是在不同的地方,一個地方把資料寫到容器,另外一個地方把資料從容器拿出來,如果實在同一個代碼塊裡寫入和讀取資料到同一個容器,應該是知道具體類型的,是不需要用到通配符的。
?通配符
?
通配符稱為無限通配符,表示不确定或者不關心類型。
extends 通配符
一般稱為上界通配符,表示的意思是:取值範圍為 (某個類的子類, 某個類]。再想想我們之前說的,通配符要解決的問題?放進去的是什麼,取出來的就應該是什麼。放資料和取資料應用在不同的場景。
通過上面的表述,容易推斷出來
<? extends E>
的集合隻能往外拿資料,因為取出來的一定是
E
,但是放進去的不知道是什麼,可能是
E
,也可能是
E
的子類,如果允許往集合裡面放東西,就不能保證放進去的是什麼,拿出來的就是什麼了。因為隻能保證拿出來的是
E
。
super 通配符
一般稱為下界通配符,表示的意思是:取值範圍為 [某個類,這個類的父類)。結合上面小節的解釋,可以推斷出
<? super S>
的集合隻能往裡面放資料,而不能從裡面拿東西,為什麼呢?因為
<? extends E>
解決的就是拿出來的問題啊,是以這個解決的就是放進去的問題啊,囧。裡面放的是下限或者下限的子類。
小結
通配符與一個規則,
PE-CS
。
-
是說,如果某個集合表示一個生産者,應該用PE
通配符。因為生産者有上限,比如生産筆的公司,上限就是能生産筆,但是不能生産布。extends
-
是說,如果某個集合表示一個消費者,應該用CS
通配符。因為消費者是有下限的,好比去買筆,購物車裡面可以放鉛筆、鋼筆或者毛筆。這些下限就是筆,就是說都是筆。super
- 同時作為生産者和消費者的情況不存在,因為你可以指定具體的泛型。
參考資料
- 深入了解 Java 泛型