Java泛型的學(xué)習(xí)和使用

前面木羹,由于對泛型擦除的思考,引出了對Java-Type體系的學(xué)習(xí)解孙。本篇坑填,就讓我們繼續(xù)對“泛型”進行研究:

JDK1.5中引入了對Java語言的多種擴展,泛型(generics)即其中之一弛姜。

1. 什么是泛型脐瑰?

泛型,即“參數(shù)化類型”廷臼,就跟在方法或構(gòu)造函數(shù)中普通的參數(shù)一樣蚪黑,當(dāng)一個方法被調(diào)用時,實參替換形參中剩,方法體被執(zhí)行忌穿。當(dāng)一個泛型聲明被調(diào)用,實際類型參數(shù)取代形式類型參數(shù)结啼。

泛型

2. 為什么需要泛型掠剑?

對于Java開發(fā)者來說,集合是泛型運用最多的地方郊愧,例如:List<String>朴译、Map<String,Integer>;試想一下属铁,如若沒有泛型泛型眠寿,當(dāng)我們對集合進行遍歷、進行元素獲取的時候焦蘑,一坨坨強制類型轉(zhuǎn)換的代碼就足以讓人發(fā)瘋盯拱,而且極易出現(xiàn)類型轉(zhuǎn)換失敗的風(fēng)險;

但是,泛型的出現(xiàn)解決了這個問題狡逢,它不但簡化了代碼宁舰,還提高了程序的安全性;類型轉(zhuǎn)換的錯誤提前到編譯期解決掉奢浑;

強制轉(zhuǎn)換
類型轉(zhuǎn)換失敗

3. 泛型的擦除

JDK1.5版本推出了泛型機制蛮艰,在此之前,Java語言中并沒有泛型的概念雀彼;當(dāng)新特性來到的時候壤蚜,必然會引起新老代碼兼容性的問題,泛型也不例外徊哑。Java為解決兼容性問題仍律,采用了擦除機制;

當(dāng)我們聲明并使用泛型的時候实柠,編譯器會幫助我們進行類型的檢查和推斷水泉,然而在代碼完成編譯后的Class文件中,泛型信息卻不復(fù)存在了窒盐,JVM在運行期間對泛型無感知草则,這樣新老代碼的兼容性迎刃而解,這也就是Java泛型的擦除蟹漓;

在方法中炕横,我們定義了List<String>、Map<String,Integer>等對象葡粒,在編譯結(jié)束之后份殿,都會變成List、Map等原始類型嗽交;對于JVM來說卿嘲,泛型的信息是不可見的;下面夫壁,我們通過反射拾枣,來觀察下!

反射

在程序運行期間盒让,泛型的約束并不存在梅肤,通過反射,可以向集合中添加任意類型對象邑茄;

此外姨蝴,當(dāng)我們通過反編譯工具查看GenericTest.class文件的時候,發(fā)現(xiàn)ArrayList對象中的泛型沒有了肺缕,這也間接證明了泛型的擦除左医;

接下來授帕,我們在通過javap命令查看生成的Class文件:


源碼
javap -c 命令

結(jié)果顯示,當(dāng)我們執(zhí)行集合的add方法的時候炒辉,泛型類型String已經(jīng)被擦除豪墅,取而代之的是Object類型泉手;當(dāng)我們執(zhí)行g(shù)et方法的時候黔寇,泛型同樣不存在,也是被當(dāng)做Object來返回斩萌;

可是缝裤,我有個疑問,在編譯期由于泛型的存在颊郎,我們不需要顯式的進行類型轉(zhuǎn)換憋飞,但是在運行期間是如何解決的呢,難道不會報錯嗎姆吭?

ArrayList--get方法
ArrayList--get方法

查看源碼發(fā)現(xiàn)榛做,ArrayList在get方法中,已經(jīng)顯式進行了類型轉(zhuǎn)換内狸;

自定義一個泛型類检眯,在get方法中不進行類型轉(zhuǎn)換的聲明,看看結(jié)果如何昆淡?

運行main方法后锰瘸,程序沒有報錯,正常結(jié)束昂灵;

通過上面的2個例子避凝,我們不僅產(chǎn)生疑問,ArrayList中聲明了類型轉(zhuǎn)換眨补,Test中沒有聲明管削,但是兩者在運行期間都沒有報錯?那么ArrayList的聲明意義何在呢 ?

當(dāng)再次查看ArrayList源碼時發(fā)現(xiàn)撑螺,elementData對象實際上是一個Object類型數(shù)組佩谣,當(dāng)我們獲取元素并返回的時候,編譯器會根據(jù)方法的返回值進行類型安全檢查实蓬,所以 return (E) elementData[index]才會有強制類型轉(zhuǎn)換的情況茸俭;

通過了解checkcast指令后,結(jié)合上面的2個例子安皱,我認(rèn)為JVM虛擬機在真正執(zhí)行g(shù)et方法的時候调鬓,實際上隱式的為我們的代碼進行了類型轉(zhuǎn)換操作,就好比在代碼中直接聲明String ss = (String)test.getT()酌伊、String sss = (String)list.get(0)一樣腾窝;

實際上缀踪,在了解到checkcast虛擬機指令后,再次證明了上面的觀點虹脯;

checkcast:“檢驗類型轉(zhuǎn)換驴娃,檢驗未通過將拋出ClassCastException”;

官方解釋:checkcast checks that the top item on the operand stack (a reference to an object or array) can be cast to a given type. For example, if you write in Java:return ((String)obj);

4. 泛型擦除帶來的問題

4.1 類型信息的丟失

由于泛型擦除機制的存在循集,在運行期間無法獲取關(guān)于泛型參數(shù)類型的任何信息唇敞,自然也就無法對類型信息進行操作;例如:instanceof 咒彤、創(chuàng)建對象等疆柔;

編譯報錯

4.2 類型擦除與多態(tài)

首先,我們先復(fù)習(xí)下多態(tài)的概念镶柱,多態(tài)出現(xiàn)的場景旷档;

簡明直譯,多態(tài)多態(tài)歇拆,多種形態(tài)鞋屈;接口下眾多的實現(xiàn)類,便是多態(tài)最顯著實現(xiàn)場景之一故觅;

其次厂庇,還有方法的重寫Overriding和重載Overloading;

重寫Overriding是父類與子類之間多態(tài)性的一種表現(xiàn)逻卖,如果在子類中定義某方法與其父類有相同的名稱和參數(shù)宋列,我們說該方法被重寫(Overriding)。子類的對象使用這個方法時评也,將調(diào)用子類中的定義炼杖,對它而言,父類中的定義如同被“屏蔽”了盗迟。

重載Overloading是一個類中多態(tài)性的一種表現(xiàn)坤邪,如果在一個類中定義了多個同名的方法,它們或有不同的參數(shù)個數(shù)或有不同的參數(shù)類型罚缕,則稱為方法的重載(Overloading)艇纺。Overloaded的方法是可以改變返回值的類型但同時參數(shù)列表也得不同。

接下來邮弹,讓我們看一個例子黔衡,來具體的分析;

父類Test


子類TestChild

由于泛型擦除的存在腌乡,在程序運行期間盟劫,Test類在JVM虛擬機中實際的形態(tài)如下:

編譯后Test類

泛型被擦除,泛型變量替換為Object對象与纽;接下來侣签,我們在看看子類TestChild代碼----setT:

@Override

public void setT(String s) {}

首先塘装,來看看set方法,實際運行期間父類Test的set方法參數(shù)為Object影所,子類的為String蹦肴;回顧下Override
的定義,“如果在子類中定義某方法與其父類有相同的名稱和參數(shù)猴娩,我們說該方法被重寫(Overriding)”阴幌;顯然,在運行期間我們子類和父類的set方法只有相同的名稱胀溺,并沒有相同的參數(shù)裂七,所以并不滿足“重寫”的定義皆看;

在看下仓坞,重載的定義,“如果在一個類中定義了多個同名的方法腰吟,它們或有不同的參數(shù)個數(shù)或有不同的參數(shù)類型无埃,則稱為方法的重載(Overloading)”。既然不是重寫毛雇,并且Test 和 TestChild又是子父類關(guān)系嫉称,那么set方法從定義上來看只有可能是重載的關(guān)系;子類繼承父類方法灵疮,在TestChild中形成重載:setT(Object t)织阅、setT(String t);

既然我們推斷是setT屬于重載震捣,那么就用代碼實現(xiàn)下即可:

測試重載

很不幸荔棉,編譯報錯,在子類中并沒有一個叫做setT(Object t)的方法蒿赢,重載不成立润樱,子類的方法依舊和父類屬于重寫關(guān)系;下面羡棵,讓我來進一步去分析:

子類TestChild繼承了父類Test壹若,并傳入泛型變量String,如果忽略泛型擦除的存在皂冰,父類Test代碼應(yīng)該變成這樣:

意淫下的父類

但實際上店展,Java在編譯期已經(jīng)將泛型變量擦除,運行期間泛型變量變成了Object秃流,沒有任何關(guān)于泛型String的信息赂蕴;我們本意是實現(xiàn)方法的重寫,但實際上變成了重載(意淫下的重載)剔应;這下可如何是好睡腿?

于是语御,JVM虛擬機采用了一個特殊的方式來解決擦除和多態(tài)之間的矛盾,橋方法由此誕生席怪;我們繼續(xù)使用javap -c 命令查看class文件应闯;

子類TestChild

截圖中,子類TestChild實際上生成了4個方法挂捻,最下面的2個方法碉纺,就是JVM所生成的橋方法,而真正實現(xiàn)方法重寫的便是這個橋方法------------setT(Object t)刻撒,而我們自己定義的@Oveerride注解只不過為了滿足編譯期的要求所存在的假象而已骨田;

這樣一來,虛擬機便解決了泛型擦?xí)投鄳B(tài)之間的矛盾声怔;那么态贤,get()是否存在上面重寫的問題呢?

答案是NONONO醋火!由于重寫(Overriding)只針對于方法名和方法參數(shù)悠汽,并不沒有強調(diào)返回值的異同。所以子類---public String getT()父類---public Object getT() 是可以形成重寫的關(guān)系芥驳!

但是柿冲,在編譯之后的class文件中,由于橋方法的存在兆旬,子類中有了2個getT()方法假抄,分別為public String getT()、public Object getT()丽猬,如果在我們實際定義方法的時候宿饱,在一個類中出現(xiàn)2個這樣的方法,是無法通過編譯器的檢查的宝鼓!

同名方法

因為以上2個方法刑棵,違背了重載的定義,重名方法必須要有不同的形參愚铡,否則編譯器會報錯蛉签!

但實際上由于橋方法是在編譯后的class文件中生成,所以我們認(rèn)為虛擬機是允許這樣的情況出現(xiàn)沥寥,JVM虛擬機認(rèn)定方法唯一的方式碍舍,不單通過方法名稱和參數(shù),還包括了方法的返回值邑雅;

4.3 異常和泛型擦除

自定義異常類片橡,還必須是帶有泛型的異常類;

編譯報錯

自定義的泛型類并不能繼承exception淮野,為什么捧书?

歸根到底吹泡,還是由于泛型擦除的存在!如果上面編譯通過经瓷,那么我們在代碼中將會看到如下情形:

捕獲異常

由于泛型擦除的存在爆哑,GenericException在編譯之后將不存在泛型信息,2次catch的異常將會變成一樣舆吮,這在Java中是不允許存在的揭朝;

此外,還有一種情況色冀,看如下代碼:

捕獲異常

由于泛型擦除的存在潭袱,T泛型變量在編譯之后將會變成Exception類型(由于extends的存在,此處不會變成Object)锋恬;根據(jù)Java中關(guān)于捕捉異常的規(guī)則:子類異常必須在最前面屯换,以此往后捕捉父類異常;所以說伶氢,以上代碼違背了Java異常規(guī)范趟径,禁止在catch中使用泛型瘪吏!


5. 自定義泛型接口癣防、泛型類和泛型方法

5.1 泛型接口

泛型接口


泛型接口

5.2 泛型類

泛型類

值得注意的是,在泛型類中掌眠,成員變量不能使用靜態(tài)修飾蕾盯,編譯報錯!

靜態(tài)修飾成員變量

由于是靜態(tài)變量蓝丙,不需要創(chuàng)建對象即可調(diào)用级遭,無法確定泛型是哪種類型,所以編譯禁止通過渺尘!當(dāng)然挫鸽,需要區(qū)分5.3章節(jié)中的情況:

5.3 泛型方法

泛型方法

在泛型方法中,自己定義的泛型變量鸥跟,與類無關(guān)丢郊;

6. 通配符與上下界

在我們實際工作中,常見的通配符有3類:

無限定通配符医咨,形式:<?>

上邊界通配符枫匾,形式:<? extends Number>

下邊界通配符,形式:<? super Number>

泛型的通配符拟淮?與我們平常所定義的T 干茉、K、V等泛型變量功能類似很泊,但是通配符角虫?只能使用在已聲明過泛型的類中沾谓,不能直接定義在類上,方法上戳鹅,屬性上搏屑;

通配符的運用

List<?> list代表著,可以向List中存入任何類型的對象粉楚,此時的辣恋?可以理解為Object;

那么模软,上邊界和下邊界又是什么意思呢伟骨?

<? extends Number>代表著所傳入的類型參數(shù)只能為Number的子類,這就是通配符的上邊界燃异;

<? super Number>代表著所傳入的類型參數(shù)只能為Number携狭、Number的父類,這就是通配符的下邊界回俐;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末逛腿,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子仅颇,更是在濱河造成了極大的恐慌单默,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件忘瓦,死亡現(xiàn)場離奇詭異搁廓,居然都是意外死亡,警方通過查閱死者的電腦和手機耕皮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門境蜕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人凌停,你說我怎么就攤上這事粱年。” “怎么了罚拟?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵台诗,是天一觀的道長。 經(jīng)常有香客問我舟舒,道長拉庶,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任秃励,我火速辦了婚禮氏仗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己皆尔,他們只是感情好呐舔,可當(dāng)我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著慷蠕,像睡著了一般珊拼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上流炕,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天澎现,我揣著相機與錄音,去河邊找鬼每辟。 笑死剑辫,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的渠欺。 我是一名探鬼主播妹蔽,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼挠将!你這毒婦竟也來了胳岂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤舔稀,失蹤者是張志新(化名)和其女友劉穎乳丰,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體镶蹋,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡成艘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了贺归。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡断箫,死狀恐怖拂酣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情仲义,我是刑警寧澤婶熬,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站埃撵,受9級特大地震影響赵颅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜暂刘,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一饺谬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧谣拣,春花似錦募寨、人聲如沸族展。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽仪缸。三九已至,卻和暖如春列肢,著一層夾襖步出監(jiān)牢的瞬間恰画,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工瓷马, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留锣尉,地道東北人。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓决采,卻偏偏與公主長得像自沧,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子树瞭,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,601評論 2 353

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

  • object 變量可指向任何類的實例拇厢,這讓你能夠創(chuàng)建可對任何數(shù)據(jù)類型進程處理的類。然而晒喷,這種方法存在幾個嚴(yán)重的問題...
    CarlDonitz閱讀 913評論 0 5
  • 泛型是Java 1.5引入的新特性孝偎。泛型的本質(zhì)是參數(shù)化類型,這種參數(shù)類型可以用在類凉敲、變量衣盾、接口和方法的創(chuàng)建中,分別...
    何時不晚閱讀 3,035評論 0 2
  • 在之前的文章中分析過了多態(tài)爷抓,可以知道多態(tài)本身是一種泛化機制势决,它通過基類或者接口來設(shè)計,使程序擁有一定的靈活性蓝撇,但是...
    _小二_閱讀 684評論 0 0
  • 多年后果复,當(dāng)我坐在出租屋的桌前,夜半歌聲渤昌,悠揚而輕快虽抄,執(zhí)筆逐字逐句回憶著過去,仿佛那些櫻花與柳絮就飛舞在眼前独柑,音樂的...
    5a23edd886c0閱讀 499評論 1 2
  • 書名:《火花》 作者:又吉直樹 這是一部小說又是一部“自傳”忌栅。故事站在主人公——漫才組合Sparks的德永——的角...
    鞠茜閱讀 318評論 0 0