Java:泛型的理解

image

本文源自參考《Think in Java》完沪,多篇博文以及閱讀源碼的總結(jié)

前言

Java中的泛型每各人都在使用饮怯,但是它底層的實現(xiàn)方法是什么呢勇蝙,為何要這樣實現(xiàn)延赌,這樣實現(xiàn)的優(yōu)缺點有哪些耽梅,怎么解決泛型帶來的問題薛窥。帶著好奇,我查閱資料進行了初步的學(xué)習(xí)褐墅,在此與諸位探討拆檬。

一 類型參數(shù)

學(xué)過JAVA的人都知道泛型洪己,明白大概怎么使用。在類上為:class 類名<T> {}竟贯,在方法上為:public <T> void 方法名 (T x){}答捕。泛型的實現(xiàn)使得類型變成了參數(shù)可以傳入,使得類功能多樣化屑那。

具體可分為5種情況:

  1. T是成員變量的類型
  2. T是泛型變量(無論成員變量還是局部變量)的類型參數(shù)拱镐,常見如Class<T>,List<T>持际。
  3. T是方法拋出的Exception(要求<T extends Exception>
  4. T是方法的返回值
  5. T是方法的參數(shù)

1.1 泛型的實現(xiàn)

JAVA的泛型是基于編譯器實現(xiàn)的沃琅,使用了擦除的方法實現(xiàn),這是因為java1.5之后才出現(xiàn)了泛型蜘欲,為了保持向后兼容而做出的妥協(xié)益眉。

所謂擦除就是JAVA文件在編譯成字節(jié)碼時類型參數(shù)會被擦除掉,單獨記錄在其他地方姥份。并且用類型參數(shù)的父類代替原有的位置郭脂。
假設(shè)參數(shù)類型的占位符為T,擦除規(guī)則如下:

  1. <T>擦除后變?yōu)?code>Obecjt
  2. <? extends A>擦除后變?yōu)?code>A
  3. <? super A>擦除后變?yōu)?code>Object

這種規(guī)則叫做保留上界

編譯器擦除類型參數(shù)后澈歉,通過JAVA的強制轉(zhuǎn)換保證了類型參數(shù)在使用時的正確展鸡。如:在類型參數(shù)T中傳入了類A,那么編譯器會在所有類A將返回(拋出)類型參數(shù)T的代碼處加上(A)進行強轉(zhuǎn).

舉個栗子:

        ArrayList<String> list = new ArrayList<String>();
        list.add("123");
        String b = list.get(0);

在編譯后會變成

        ArrayList list = new ArrayList();//沒有參數(shù)即默認為Object
        list.add("123");
        String b = (String) list.get(0);

并且會在帶有類型參數(shù)類的子類中形成橋方法保證了多態(tài)性。
具體參考官方解釋如下

  • 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.

二 通配符?

在帶有類型參數(shù)的類內(nèi)部埃难,代碼仍然按照參數(shù)類型擦除后的父類來處理莹弊。但是擦除存在一個問題,在這種機制下泛型是不變的涡尘,而沒有逆變和協(xié)變忍弛。

2.1 逆變與協(xié)變


協(xié)變和逆變網(wǎng)上有很多解釋,顯得模糊不清悟衩,我參考幾個編程語言的官方解釋后給出一個比較寬泛的定義剧罩。協(xié)變指能夠使用比原始聲明類型的派生程度更大(更具體的)的類型,逆變指能夠使用比原始聲明類型的派生程度更凶尽(不太具體的)的類型。
如:
Object obj = new String("123");
這就是協(xié)變幕与,將String這個更具體的(子類)類型賦給了原本較寬泛定義(父類)的類型Object挑势。
JAVA不允許將父類賦給子類,自然Java不支持逆變啦鸣。

網(wǎng)上很多博文說JAVA泛型也有逆變潮饱,我是不贊同的,那只是一種模擬的逆變诫给,即有部分逆變的特性而且看起來像逆變香拉,具體分析后文會給出


2.2 Java中的逆變與協(xié)變

在JAVA中啦扬,

List<Integer> b = new ArrayList<Integer>()
List<NumFber> a = b;

是無法通過編譯器檢查的。不允許這樣做有一個很充分的理由:這樣做將破壞要泛型的類型安全凫碌。如果能夠?qū)?code>List<Integer> 賦給List<Number>扑毡。那么下面的代碼就允許將非Integer的內(nèi)容放入 List<Integer>

List<Integer> b = new ArrayList<Integer>(); 
List<Number> a = b; // illegal 
a.add(new Float(3.1415));

因為aList<Number>,所以向其添加Float似乎是完全可行的盛险。但是如果a實際是List<Integer>瞄摊,那么這就破壞了蘊含在b中定義的類型聲明 —— 它是一個整數(shù)列表,這就是泛型類型不能協(xié)變的原因苦掘。但也因此使得泛型失去了多態(tài)的拓展性换帜。

2.3 通配符解決協(xié)變

Java官方通過加入了通配符?來解決泛型協(xié)變的問題。這樣就能通過編譯了:

List<Integer> b = new ArrayList<Integer>(); 
List<? extends Number> a = b;

可以解讀為a是一種帶有NumberList集合類鹤啡,在從a中取出數(shù)據(jù)的時候統(tǒng)一當(dāng)做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的父類疲眷。這也符合里氏替換原則的

(網(wǎng)上大部分博文說這就是逆變,但是仔細想想逆變的官方定義,在JAVA中可以理解為:類T是類S的子類您朽,而類A<T>是類A<S>的父類狂丝。仔細看看List<? super Number>List<Object>的關(guān)系,在這里TNumber,而SObject哗总,但是List<? super Number>從邏輯上來看真的是List<Object>的子類嗎几颜,如果單純從字面上來看List<? super Number>是帶有Number父類的集合類,根據(jù)保留上界的擦除方法,應(yīng)該擦除為List<Object>,將一個List<Object>賦給另一個List<Object>是不存在任何逆變的讯屈。我在疑惑之下進行了科學(xué)上網(wǎng)查閱資料蛋哭,也沒有英文資料說明JAVA泛型里這屬于逆變)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市涮母,隨后出現(xiàn)的幾起案子谆趾,更是在濱河造成了極大的恐慌,老刑警劉巖叛本,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件沪蓬,死亡現(xiàn)場離奇詭異,居然都是意外死亡来候,警方通過查閱死者的電腦和手機跷叉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人云挟,你說我怎么就攤上這事梆砸。” “怎么了园欣?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵帖世,是天一觀的道長。 經(jīng)常有香客問我俊庇,道長狮暑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任辉饱,我火速辦了婚禮搬男,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘彭沼。我一直安慰自己缔逛,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布姓惑。 她就那樣靜靜地躺著褐奴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪于毙。 梳的紋絲不亂的頭發(fā)上敦冬,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天,我揣著相機與錄音唯沮,去河邊找鬼脖旱。 笑死,一個胖子當(dāng)著我的面吹牛介蛉,可吹牛的內(nèi)容都是我干的萌庆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼币旧,長吁一口氣:“原來是場噩夢啊……” “哼践险!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起吹菱,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤巍虫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后鳍刷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體垫言,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年倾剿,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡前痘,死狀恐怖凛捏,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情芹缔,我是刑警寧澤坯癣,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站最欠,受9級特大地震影響示罗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜芝硬,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一蚜点、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧拌阴,春花似錦绍绘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至纤壁,卻和暖如春左刽,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背酌媒。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工欠痴, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人馍佑。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓斋否,卻偏偏與公主長得像,于是被迫代替她去往敵國和親拭荤。 傳聞我的和親對象是個殘疾皇子茵臭,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 一、泛型擦除 在編譯期間舅世,所有泛型信息都會被擦除掉旦委,在生成的字節(jié)碼中是不包括泛型中的類型信息的 直接舉個例子 看一...
    aweika閱讀 492評論 0 1
  • 1.泛型簡介 問題:在獲取用戶信息的API中,后臺給我們返回一個這樣形式的json字符串雏亚。{ "meta":...
    彼岸之城cyy閱讀 967評論 0 0
  • 參考地址:《Java 泛型缨硝,你了解類型擦除嗎?》 《Java中的逆變與協(xié)變》 《java 泛型中 T罢低、E .....
    琦小蝦閱讀 3,019評論 0 11
  • http://www.reibang.com/p/7e3e2b898143這是上次寫的泛型查辩,當(dāng)時其實還是一知半解胖笛。...
    Richardo92閱讀 389評論 0 1
  • 2018年04月12日 前些天萍倡,在去吃飯的路上身弊,一個自稱邦德物流員工的人,頂著炎熱列敲,在公司園區(qū)的門口阱佛,兜售捷安特的...
    丁肖東01閱讀 1,532評論 0 3