漫談Java的泛型機制

泛型是Java 1.5引入的新特性桩砰。泛型的本質(zhì)是參數(shù)化類型拓春,這種參數(shù)類型可以用在類、變量亚隅、接口和方法的創(chuàng)建中硼莽,分別稱為泛型類、泛型變量枢步、泛型接口、泛型方法渐尿。將集合聲明參數(shù)化以及使用JDK提供的泛型和泛型方法是相對簡單的醉途,而編寫自己的泛型類型會比較困難,但是還是值得思考與學習如何去編寫砖茸。

1隘擎、泛型的優(yōu)勢

提高代碼的安全性和表述性
在沒有泛型的情況的下,通過對類型Object的引用來實現(xiàn)參數(shù)的“任意化”凉夯,缺點是要做顯式的強制類型轉(zhuǎn)換货葬,而這種轉(zhuǎn)換是要求開發(fā)者對實際參數(shù)類型可以預(yù)知的情況下進行的。一個錯誤的示范如下:

public static void main(String[] args) {
        List list = new ArrayList();
        list.add(1);
        list.add("String");
        int isInt = (int) list.get(1); //ClassCastException
    }

本例中對于強制類型轉(zhuǎn)換錯誤的情況劲够,編譯器在編譯時并不提示錯誤震桶,在運行的時候才出現(xiàn)ClassCastException異常,這樣便存在著安全隱患征绎。(Effective Java第23條:請不要在新代碼中使用原生態(tài)類型)
提高代碼的重用率
利用泛型類可以選擇具體的類型對類進行復(fù)用相對比較容易理解蹲姐,具體的說明如下:

public class Box<T> {
    private T t;
    public void set(T t) { this.t = t; }
    public T get() { return t; }
}

這樣我們的Box類便可以得到復(fù)用,我們可以將T替換成任何我們想要的類型:

Box<Integer> integerBox = new Box<Integer>();
Box<Double> doubleBox = new Box<Double>();
Box<String> stringBox = new Box<String>();

2人柿、泛型的使用

泛型類
泛型類中使用通配泛型T相比較用Object類型強制轉(zhuǎn)換的優(yōu)勢已經(jīng)介紹過柴墩,詳見章節(jié)1中Box類中泛型的使用。
泛型方法
泛型類在多個方法簽名間實施類型約束凫岖。在 List<V> 中江咳,類型參數(shù) V出現(xiàn)在 get()add()哥放、contains() 等方法的簽名中歼指。當創(chuàng)建一個 Map<K, V> 類型的變量時爹土,您就在方法之間宣稱一個類型約束。您傳遞給 add() 的值將與 get() 返回的值的類型相同东臀。
類似地着饥,之所以聲明泛型方法,一般是因為您想要在該方法的多個參數(shù)之間宣稱一個類型約束惰赋。舉例如下:

public static void main(String[] args) throws ClassNotFoundException {  
       String str=get("Hello", "World");  
       System.out.println(str);  
}  
public static <T, U> T get(T t, U u) {  
       if (u != null)  
            return t;  
       else  
            return null;  
}  

泛型變量
在泛型類宰掉、泛型方法的介紹中,我們已經(jīng)使用到了泛型變量赁濒,申明泛型變量主要是因為我們在定義泛型變量的時候轨奄,我們并不知道這個泛型類型T,到底是什么類型拒炎,所以挪拟,只能默認T為原始類型Object,而是使用時確定泛型T的具體類型击你,也是用來做類型限定的玉组。
通配符
通配泛型的使用相對基本的泛型類型的使用而言具有一定的難度,不過通配符可以提高API的靈活性丁侄。舉例如下定義3個類:

class Fruit {}
class Apple extends Fruit {}
class Orange extends Fruit {}

通過通配泛型惯雳,可以定義出受檢的泛型類型,也能夠?qū)讉€類的關(guān)系體現(xiàn)出來鸿摇。

List<? extends Fruit> flist = new ArrayList<Fruit>();
List<? extends Fruit> flist = new ArrayList<Apple>();
List<? extends Fruit> flist = new ArrayList<Orange>();

3石景、數(shù)組與泛型

數(shù)組與泛型相比,有兩個重要的不同點拙吉。首先潮孽,數(shù)組是協(xié)變的(covariant)。這就是說如果subsuper的子類型筷黔,那么數(shù)組類型sub[]就是super[]的子類型往史。然而,泛型是不可變的(invariant)佛舱,對于任意兩個不同的類型type1type2怠堪,List<type1>既不是List<type1>的子類型,也不是List<type2>的超類型名眉。

數(shù)組和泛型的第二大區(qū)別在于數(shù)組是具體化的粟矿,因此數(shù)組會在運行時才知道并檢查他們的元素類型約束。相比之下损拢,泛型是通過擦除來實現(xiàn)的陌粹,因此泛型只在編譯時強化他們的類型信息,并在運行時丟棄他們的元素類型信息福压。

Object[] arr = new Long[1];
arr[0] = "I don't fit in"; //運行失敗掏秩,拋出ArrayStoreException

List<Object> list = new ArrayList<>(); //編譯不通過或舞,類型不匹配
list.add(I don't fit in);

(Effective Java第25條:列表優(yōu)先于數(shù)組)
由于以上這些根本的區(qū)別,數(shù)組和泛型不能很好的混合使用蒙幻,例如:創(chuàng)建泛型或者類型參數(shù)的數(shù)組是非法的映凳。

4、類型擦除

不同的語言在實現(xiàn)泛型時采用的方式不同邮破,C++的模板會在編譯時根據(jù)參數(shù)類型的不同生成不同的代碼诈豌,而Java的泛型是一種偽泛型,編譯為字節(jié)碼時參數(shù)類型會在代碼中被擦除抒和,單獨記錄在Class文件的attributes域矫渔,而在使用泛型處做類型檢查與類型轉(zhuǎn)換。
TIPS: 區(qū)別Java語言的編譯時運行時是非常重要的摧莽,泛型只在編譯時強化他們的類型信息庙洼,并在運行時丟棄他們的元素類型信息。泛型的運行時擦除可以通過Java提供的反射機制進行證明镊辕,比如通過反射調(diào)用List<String>容器的add()方法油够,繞過泛型檢查,成功插入Integer類型的變量征懈。

假設(shè)參數(shù)類型的占位符為T石咬,擦除規(guī)則如下:

  • <T>擦除后變?yōu)?code>Obecjt
  • <? extends A>擦除后變?yōu)?code>A
    *<? super A>擦除后變?yōu)?code>Object

上述擦除規(guī)則叫做保留上界受裹。泛型擦除之后保留原始類型碌补。原始類型raw type就是擦除去了泛型信息虏束,最后在字節(jié)碼中的類型變量的真正類型棉饶。無論何時定義一個泛型類型,相應(yīng)的原始類型都會被自動地提供镇匀。類型變量被擦除crased照藻,并使用其限定類型(無限定的變量用Object)替換。

但是要區(qū)分原始類型和泛型變量的類型
在調(diào)用泛型方法的時候汗侵,可以指定泛型幸缕,也可以不指定泛型。
在不指定泛型的情況下晰韵,泛型變量的類型為 該方法中的幾種類型的同一個父類的最小級发乔,直到Object。
在指定泛型的時候雪猪,該方法中的幾種類型必須是該泛型實例類型或者其子類栏尚。

  public class Test2{
      public static void main(String[] args) {
           /**不指定泛型的時候*/
          int i=Test2.add(1, 2); //兩參數(shù)都是Integer,所以T為Integer類型
          Number f=Test2.add(1 , 1.2);//參數(shù)是Integer和Float只恨,取同一父類的最小級Number
          Object o=Test2.add(1, "asd"); //參數(shù)是Integer和String译仗,取同一父類的最小級Object
        
          /**指定泛型的時候*/
          int a=Test2.<Integer>add(1, 2);//指定了Integer抬虽,所以只能為Integer類型或者其子類
          int b=Test2.<Integer>add(1 , 2.2);//編譯錯誤,指定了Integer纵菌,不能為Float
          Number c=Test2.<Number>add(1,  2.2);  //指定為Number阐污,所以可以為Integer和Float
     }  

     //這是一個簡單的泛型方法
      public static <T> T add(T x,T y){
              return y;
      }  
  }

5、類型擦除的問題和解決方法

Java的泛型是偽泛型咱圆。為什么說Java的泛型是偽泛型呢笛辟?因為,在編譯期間闷堡,所有的泛型信息都會被擦除掉隘膘。正確理解泛型概念的首要前提是理解類型擦出(type erasure)。Java中的泛型基本上都是在編譯器這個層次來實現(xiàn)的杠览。在生成的Java字節(jié)碼中是不包含泛型中的類型信息的弯菊。使用泛型的時候加上的類型參數(shù),會在編譯器在編譯的時候去掉踱阿。這個過程就稱為類型擦除管钳。
因為種種原因,Java不能實現(xiàn)真正的泛型软舌,只能使用類型擦除來實現(xiàn)偽泛型才漆,這樣雖然不會有類型膨脹的問題,但是也引起了許多新的問題佛点。所以醇滥,Sun對這些問題作出了許多限制,避免我們犯各種錯誤超营。
1鸳玩、先檢查,在編譯演闭,以及檢查編譯的對象和引用傳遞的問題

2不跟、自動類型轉(zhuǎn)換
因為類型擦除的問題,所以所有的泛型類型變量最后都會被替換為原始類型米碰。這樣就引起了一個問題窝革,既然都被替換為原始類型,那么為什么我們在獲取的時候吕座,不需要進行強制類型轉(zhuǎn)換呢虐译?實際上,使用泛型的容器會在return之前吴趴,會根據(jù)泛型變量進行強轉(zhuǎn)漆诽。

3、類型擦除與多態(tài)的沖突和解決方法
子類實現(xiàn)父類中的泛型的方法時注意因為擦除而引起的語義的變化

4、泛型類型變量不能是基本數(shù)據(jù)類型
不能用類型參數(shù)替換基本類型拴泌。就比如魏身,沒有ArrayList<double>,只有ArrayList<Double>蚪腐。因為當類型擦除后箭昵,ArrayList的原始類型變?yōu)?code>Object,但是Object類型不能存儲double值回季,只能引用Double的值家制。

5、運行時類型查詢
由于運行時類型已經(jīng)擦除泡一,所以進行泛型類型的查詢是不正確的颤殴,對泛型的類型查詢Java限定了這種類型查詢的方式if( arrayList instanceof ArrayList<?>)

6、異常中使用泛型的問題
不能拋出也不能捕獲泛型類的對象鼻忠。因為異常都是在運行時捕獲和拋出的涵但,而在編譯的時候,泛型信息全都會被擦除掉帖蔓,類型信息被擦除后矮瘟,那么很有可能兩個地方的catch都變?yōu)樵碱愋?code>Object,這個當然就是不行的塑娇。就好比澈侠,catch兩個一模一樣的普通異常,不能通過編譯一樣埋酬。
根據(jù)異常捕獲的原則哨啃,一定是子類在前面,父類在后面写妥,那么上面就違背了這個原則拳球。即使你在使用該靜態(tài)方法的使用T是ArrayIndexOutofBounds,在編譯之后還是會變成Throwable耳标,ArrayIndexOutofBoundsIndexOutofBounds的子類醇坝,違背了異常捕獲的原則邑跪。所以Java為了避免這樣的情況次坡,禁止在catch子句中使用泛型變量。

7画畅、泛型類型的實例化
不能實例化泛型類型

8砸琅、類型擦除后的沖突
當泛型類型被擦除后,創(chuàng)建條件不能產(chǎn)生沖突轴踱,如下代碼段中泛型擦除后方法
boolean equals(T)變成了方法boolean equals(Object)這與Object.equals方法是沖突的症脂!當然,補救的辦法是重新命名引發(fā)錯誤的方法。

class Pair<T>   {  
    public boolean equals(T value) {  
        return null;  
    }      
}  

9诱篷、泛型在靜態(tài)方法和靜態(tài)類中的問題
泛型類中的靜態(tài)方法和靜態(tài)變量不可以使用泛型類所聲明的泛型類型參數(shù)壶唤。因為泛型類中的泛型參數(shù)的實例化是在定義對象的時候指定的,而靜態(tài)變量和靜態(tài)方法不需要使用對象來調(diào)用棕所。對象都沒有創(chuàng)建闸盔,如何確定這個泛型參數(shù)是何種類型,所以當然是錯誤的琳省。但是要注意區(qū)分一種情況迎吵,在泛型方法中使用的T是自己在方法中定義的T,而不是泛型類中的T针贬,是沒有錯誤的击费。

public class Test2<T> {    
    public static T one;   //編譯錯誤    
    public static  T show(T one){ //編譯錯誤    
        return null;    
    }    
    public static <T>T show(T one){//這是正確的    
        return null;    
    } 
}  

參考資料:

[1]:《Effective Java》
[2]:關(guān)于Java泛型深入理解小總結(jié)
[3]:Java泛型詳解
[4]:Java泛型的實現(xiàn):原理與問題
[5]:Java中的逆變與協(xié)變
[6]:java泛型(一)、泛型的基本介紹和使用
[7]:java泛型(二)桦他、泛型的內(nèi)部原理:類型擦除以及類型擦除帶來的問題

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蔫巩,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子快压,更是在濱河造成了極大的恐慌批幌,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嗓节,死亡現(xiàn)場離奇詭異荧缘,居然都是意外死亡,警方通過查閱死者的電腦和手機拦宣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門截粗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人鸵隧,你說我怎么就攤上這事绸罗。” “怎么了豆瘫?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵珊蟀,是天一觀的道長。 經(jīng)常有香客問我外驱,道長育灸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任昵宇,我火速辦了婚禮磅崭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘瓦哎。我一直安慰自己砸喻,他們只是感情好柔逼,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著割岛,像睡著了一般愉适。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上癣漆,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天儡毕,我揣著相機與錄音,去河邊找鬼扑媚。 笑死腰湾,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的疆股。 我是一名探鬼主播费坊,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼旬痹!你這毒婦竟也來了附井?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤两残,失蹤者是張志新(化名)和其女友劉穎永毅,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體人弓,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡沼死,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了崔赌。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片意蛀。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖健芭,靈堂內(nèi)的尸體忽然破棺而出县钥,到底是詐尸還是另有隱情,我是刑警寧澤慈迈,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布若贮,位于F島的核電站,受9級特大地震影響痒留,放射性物質(zhì)發(fā)生泄漏谴麦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一狭瞎、第九天 我趴在偏房一處隱蔽的房頂上張望细移。 院中可真熱鬧搏予,春花似錦熊锭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽精绎。三九已至,卻和暖如春锌妻,著一層夾襖步出監(jiān)牢的瞬間代乃,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工仿粹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留搁吓,地道東北人。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓吭历,卻偏偏與公主長得像堕仔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子晌区,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354

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

  • 一摩骨、為什么要使用泛型 1.類型參數(shù)的好處 類型安全:泛型的主要目標是提高 Java 程序的類型安全。通過知道使用泛...
    SeanMa閱讀 7,075評論 1 18
  • 文章作者:Tyan博客:noahsnail.com 1. 什么是泛型 Java泛型(Generics)是JDK 5...
    SnailTyan閱讀 773評論 0 3
  • 引言:泛型一直是困擾自己的一個難題朗若,但是泛型有時一個面試時老生常談的問題恼五;今天作者就通過查閱相關(guān)資料簡單談?wù)勛约簩?..
    cp_insist閱讀 1,844評論 0 4
  • 姑娘灾馒,我得慶幸即使是我從你的這一段旅程里路過,我也在你歲月明媚的青春里盛裝出席遣总,與你走過被放牧的時光你虹。愿你的...
    灰魚哀莉?qū)憰?/span>閱讀 410評論 0 0
  • 招聘面試會整整進行了一天的時間,寶玉他們才稍微地休息了一下彤避,他們把前來應(yīng)聘的人全部安排好傅物,最后他們才發(fā)現(xiàn)他們已經(jīng)連...
    可可豆子閱讀 611評論 0 2