Java泛型的學習和使用

前面,由于對泛型擦除的思考,引出了對Java-Type體系的學習。本篇,就讓我們繼續對“泛型”進行研究:

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

1. 什么是泛型?

泛型,即“參數化類型”,就跟在方法或構造函數中普通的參數一樣,當一個方法被調用時,實參替換形參,方法體被執行。當一個泛型聲明被調用,實際類型參數取代形式類型參數。

泛型

2. 為什么需要泛型?

對于Java開發者來說,集合是泛型運用最多的地方,例如:List<String>、Map<String,Integer>;試想一下,如若沒有泛型泛型,當我們對集合進行遍歷、進行元素獲取的時候,一坨坨強制類型轉換的代碼就足以讓人發瘋,而且極易出現類型轉換失敗的風險;

但是,泛型的出現解決了這個問題,它不但簡化了代碼,還提高了程序的安全性;類型轉換的錯誤提前到編譯期解決掉;

強制轉換
類型轉換失敗

3. 泛型的擦除

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

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

在方法中,我們定義了List<String>、Map<String,Integer>等對象,在編譯結束之后,都會變成List、Map等原始類型;對于JVM來說,泛型的信息是不可見的;下面,我們通過反射,來觀察下!

反射

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

此外,當我們通過反編譯工具查看GenericTest.class文件的時候,發現ArrayList對象中的泛型沒有了,這也間接證明了泛型的擦除;

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


源碼
javap -c 命令

結果顯示,當我們執行集合的add方法的時候,泛型類型String已經被擦除,取而代之的是Object類型;當我們執行get方法的時候,泛型同樣不存在,也是被當做Object來返回;

可是,我有個疑問,在編譯期由于泛型的存在,我們不需要顯式的進行類型轉換,但是在運行期間是如何解決的呢,難道不會報錯嗎?

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

查看源碼發現,ArrayList在get方法中,已經顯式進行了類型轉換;

自定義一個泛型類,在get方法中不進行類型轉換的聲明,看看結果如何?

運行main方法后,程序沒有報錯,正常結束;

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

當再次查看ArrayList源碼時發現,elementData對象實際上是一個Object類型數組,當我們獲取元素并返回的時候,編譯器會根據方法的返回值進行類型安全檢查,所以 return (E) elementData[index]才會有強制類型轉換的情況;

通過了解checkcast指令后,結合上面的2個例子,我認為JVM虛擬機在真正執行get方法的時候,實際上隱式的為我們的代碼進行了類型轉換操作,就好比在代碼中直接聲明String ss = (String)test.getT()、String sss = (String)list.get(0)一樣;

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

checkcast:“檢驗類型轉換,檢驗未通過將拋出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 類型信息的丟失

由于泛型擦除機制的存在,在運行期間無法獲取關于泛型參數類型的任何信息,自然也就無法對類型信息進行操作;例如:instanceof 、創建對象等;

編譯報錯

4.2 類型擦除與多態

首先,我們先復習下多態的概念,多態出現的場景;

簡明直譯,多態多態,多種形態;接口下眾多的實現類,便是多態最顯著實現場景之一;

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

重寫Overriding是父類與子類之間多態性的一種表現,如果在子類中定義某方法與其父類有相同的名稱和參數,我們說該方法被重寫(Overriding)。子類的對象使用這個方法時,將調用子類中的定義,對它而言,父類中的定義如同被“屏蔽”了。

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

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

父類Test


子類TestChild

由于泛型擦除的存在,在程序運行期間,Test類在JVM虛擬機中實際的形態如下:

編譯后Test類

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

@Override

public void setT(String s) {}

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

在看下,重載的定義,“如果在一個類中定義了多個同名的方法,它們或有不同的參數個數或有不同的參數類型,則稱為方法的重載(Overloading)”。既然不是重寫,并且Test 和 TestChild又是子父類關系,那么set方法從定義上來看只有可能是重載的關系;子類繼承父類方法,在TestChild中形成重載:setT(Object t)、setT(String t);

既然我們推斷是setT屬于重載,那么就用代碼實現下即可:

測試重載

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

子類TestChild繼承了父類Test,并傳入泛型變量String,如果忽略泛型擦除的存在,父類Test代碼應該變成這樣:

意淫下的父類

但實際上,Java在編譯期已經將泛型變量擦除,運行期間泛型變量變成了Object,沒有任何關于泛型String的信息;我們本意是實現方法的重寫,但實際上變成了重載(意淫下的重載);這下可如何是好?

于是,JVM虛擬機采用了一個特殊的方式來解決擦除和多態之間的矛盾,橋方法由此誕生;我們繼續使用javap -c 命令查看class文件;

子類TestChild

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

這樣一來,虛擬機便解決了泛型擦書和多態之間的矛盾;那么,get()是否存在上面重寫的問題呢?

答案是NONONO!由于重寫(Overriding)只針對于方法名和方法參數,并不沒有強調返回值的異同。所以子類---public String getT()父類---public Object getT() 是可以形成重寫的關系!

但是,在編譯之后的class文件中,由于橋方法的存在,子類中有了2個getT()方法,分別為public String getT()、public Object getT(),如果在我們實際定義方法的時候,在一個類中出現2個這樣的方法,是無法通過編譯器的檢查的!

同名方法

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

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

4.3 異常和泛型擦除

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

編譯報錯

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

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

捕獲異常

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

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

捕獲異常

由于泛型擦除的存在,T泛型變量在編譯之后將會變成Exception類型(由于extends的存在,此處不會變成Object);根據Java中關于捕捉異常的規則:子類異常必須在最前面,以此往后捕捉父類異常;所以說,以上代碼違背了Java異常規范,禁止在catch中使用泛型!


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

5.1 泛型接口

泛型接口


泛型接口

5.2 泛型類

泛型類

值得注意的是,在泛型類中,成員變量不能使用靜態修飾,編譯報錯!

靜態修飾成員變量

由于是靜態變量,不需要創建對象即可調用,無法確定泛型是哪種類型,所以編譯禁止通過!當然,需要區分5.3章節中的情況:

5.3 泛型方法

泛型方法

在泛型方法中,自己定義的泛型變量,與類無關;

6. 通配符與上下界

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

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

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

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

泛型的通配符?與我們平常所定義的T 、K、V等泛型變量功能類似,但是通配符?只能使用在已聲明過泛型的類中,不能直接定義在類上,方法上,屬性上;

通配符的運用

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

那么,上邊界和下邊界又是什么意思呢?

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

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容

  • object 變量可指向任何類的實例,這讓你能夠創建可對任何數據類型進程處理的類。然而,這種方法存在幾個嚴重的問題...
    CarlDonitz閱讀 926評論 0 5
  • 泛型是Java 1.5引入的新特性。泛型的本質是參數化類型,這種參數類型可以用在類、變量、接口和方法的創建中,分別...
    何時不晚閱讀 3,047評論 0 2
  • 在之前的文章中分析過了多態,可以知道多態本身是一種泛化機制,它通過基類或者接口來設計,使程序擁有一定的靈活性,但是...
    _小二_閱讀 694評論 0 0
  • 多年后,當我坐在出租屋的桌前,夜半歌聲,悠揚而輕快,執筆逐字逐句回憶著過去,仿佛那些櫻花與柳絮就飛舞在眼前,音樂的...
    5a23edd886c0閱讀 514評論 1 2
  • 書名:《火花》 作者:又吉直樹 這是一部小說又是一部“自傳”。故事站在主人公——漫才組合Sparks的德永——的角...
    鞠茜閱讀 335評論 0 0