本文源自參考《Think in Java》,多篇博文以及閱讀源碼的總結java
Java中的泛型每各人都在使用,可是它底層的實現方法是什麼呢,爲什麼要這樣實現,這樣實現的優缺點有哪些,怎麼解決泛型帶來的問題。帶着好奇,我查閱資料進行了初步的學習,在此與諸位探討。編程
學過JAVA的人都知道泛型,明白大概怎麼使用。在類上爲:class 類名
{},在方法上爲:public 安全void 方法名 (T x){}。泛型的實現使得類型變成了參數能夠傳入,使得類功能多樣化。
具體可分爲5種狀況:編程語言
Class<T
>,List<T>
。<T extends Exception>
)JAVA的泛型是基於編譯器實現的,使用了擦除的方法實現,這是由於java1.5以後纔出現了泛型,爲了保持向後兼容而作出的妥協。學習
所謂擦除就是JAVA文件在編譯成字節碼時類型參數會被擦除掉,單獨記錄在其餘地方。而且用類型參數的父類代替原有的位置。
假設參數類型的佔位符爲T,擦除規則以下:code
<T>
擦除後變爲Obecjt
<? extends A>
擦除後變爲A
<? super A>
擦除後變爲Object
這種規則叫作保留上界對象
編譯器擦除類型參數後,經過JAVA的強制轉換保證了類型參數在使用時的正確。如:在類型參數T中傳入了類A,那麼編譯器會在全部類A將返回(拋出)類型參數T的代碼處加上(A)進行強轉.blog
舉個栗子:get
ArrayList<String> list = new ArrayList<String>(); list.add("123"); String b = list.get(0);
在編譯後會變成編譯器
ArrayList list = new ArrayList();//沒有參數即默認爲Object list.add("123"); String b = (String) list.get(0);
而且會在帶有類型參數類的子類中造成橋方法保證了多態性。
具體參考官方解釋以下
- Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded. The produced bytecode, therefore, contains only ordinary classes, interfaces, and methods.
- Insert type casts if necessary to preserve type safety.
- Generate bridge methods to preserve polymorphism in extended generic types.
在帶有類型參數的類內部,代碼仍然按照參數類型擦除後的父類來處理。可是擦除存在一個問題,在這種機制下泛型是不變的,而沒有逆變和協變。
協變和逆變網上有不少解釋,顯得模糊不清,我參考幾個編程語言的官方解釋後給出一個比較寬泛的定義。協變指可以使用比原始聲明類型的派生程度更大(更具體的)的類型,逆變指可以使用比原始聲明類型的派生程度更小(不太具體的)的類型。
如:
Object obj = new String("123");
這就是協變,將String這個更具體的(子類)類型賦給了本來較寬泛定義(父類)的類型Object。
JAVA不容許將父類賦給子類,天然Java不支持逆變。
網上不少博文說JAVA泛型也有逆變,我是不贊同的,那只是一種模擬的逆變,即有部分逆變的特性並且看起來像逆變,具體分析後文會給出
在JAVA中,
List<Integer> b = new ArrayList<Integer>() List<NumFber> a = b;
是沒法經過編譯器檢查的。不容許這樣作有一個很充分的理由:這樣作將破壞要泛型的類型安全。若是可以將List<Integer>
賦給List<Number>
。那麼下面的代碼就容許將非Integer
的內容放入 List<Integer>
:
List<Integer> b = new ArrayList<Integer>(); List<Number> a = b; // illegal a.add(new Float(3.1415));
由於a
是List<Number>
,因此向其添加Float
彷佛是徹底可行的。可是若是a
實際是List<Integer>
,那麼這就破壞了蘊含在b
中定義的類型聲明 —— 它是一個整數列表,這就是泛型類型不能協變的緣由。但也所以使得泛型失去了多態的拓展性。
Java官方經過加入了通配符?
來解決泛型協變的問題。這樣就能經過編譯了:
List<Integer> b = new ArrayList<Integer>(); List<? extends Number> a = b;
能夠解讀爲a
是一種帶有Number
的List
集合類,在從a
中取出數據的時候統一當作Number
處理就好了。同時這也是符合里氏替換原則的
可是編譯器會禁止你將將類Integer
放入a,即a.add(new Integer(1))//illegal
這也很合理,由於你聲明的a
原本就沒有限定a
包含的具體是哪一個Number
子類,所以不許任何變量的添加保證了泛型的安全性。
解決往a
添加對象的方法也很簡單
List<Object> b = new ArrayList<Object>(); List<? super Number> a = b;
a
是某種Number
父類的List
集合類,將ArrayList<Object>
賦給a
也是合情合理的,Object
確實是Number
的父類。這也符合里氏替換原則的
(網上大部分博文說這就是逆變,可是仔細想一想逆變的官方定義,在JAVA中能夠理解爲:類T
是類S
的子類,而類A<T>
是類A<S>
的父類。仔細看看List<? super Number>
和List<Object>
的關係,在這裏T
是Number
,而S
是Object
,可是List<? super Number>
從邏輯上來看真的是List<Object>
的子類嗎,若是單純從字面上來看List<? super Number>
是帶有Number
父類的集合類,根據保留上界的擦除方法,應該擦除爲List<Object>
,將一個List<Object>
賦給另外一個List<Object>
是不存在任何逆變的。我在疑惑之下去谷歌查閱了資料,也沒有英文資料說明JAVA泛型裏這屬於逆變)