Java泛型詳解

2.6 Java泛型詳解

Java泛型是JDK5中引入的一個(gè)新特性,允許在定義類和接口的時(shí)候使用類型參數(shù)(type parameter),聲明的類型參數(shù)在使用時(shí)用具體的類型來替換蝴乔。泛型最主要的應(yīng)用是在JDK5中的新集合類框架中。

2.6.1 類型擦除

首先我們看一下Java泛型中的類型擦除:在生成的Java字節(jié)碼中是不包含泛型的類型信息的癞尚,使用泛型的時(shí)候加上的類型參數(shù)在編譯的時(shí)候會(huì)被編譯器去掉毡惜,這個(gè)過程就稱為類型擦除渊胸。如在代碼中定義的List<Object>和List<String>等類型橱健,在編譯之后都會(huì)變成List而钞。JVM看到的只是List,而由泛型附加的類型信息對(duì)JVM來說是不可見的(反射是可見的拘荡,具體可參見2.3中筆記)臼节,這一點(diǎn)和C++模板機(jī)制實(shí)現(xiàn)泛型有很大區(qū)別。

很多泛型的奇怪特性都與類型擦除有關(guān),比如:

  1. 泛型類并沒有自己獨(dú)有的Class類對(duì)象网缝。比如并不存在List<String>.class或者List<Integer>.class巨税,而只有List.class。
  2. 靜態(tài)變量是被泛型類的所有實(shí)例所共享的途凫。對(duì)于聲明為MyClass<T>的類垢夹,訪問其中的靜態(tài)變量的方法仍然是MyClass.myStaticVar溢吻。不管是通過new MyClass<String>還是new MyClass<Integer>創(chuàng)建的對(duì)象维费,都是共享一個(gè)靜態(tài)變量。
  3. 泛型的類型參數(shù)不能用在Java異常處理的catch語句中促王。因?yàn)楫惓L幚硎怯蒍VM在運(yùn)行時(shí)刻來進(jìn)行的犀盟。由于類型信息被擦除,JVM無法區(qū)分兩個(gè)異常類型MyException<String>和 MyException<Integer>的蝇狼,對(duì)于JVM來說阅畴,它們都是MyException類型的,也就無法執(zhí)行與異常對(duì)應(yīng)的catch語句迅耘。

類型擦除的基本過程也比較簡單贱枣,首先是找到用來替換類型參數(shù)的具體類,一般是Object颤专,如果指定了類型參數(shù)的上界的話纽哥,則是這個(gè)上界。然后把代碼中的類型參數(shù)都替換成該類栖秕。同時(shí)去掉出現(xiàn)的類型聲明春塌,即去掉<>的內(nèi)容。比如T get()方法聲明就變成了Object get()簇捍;List<String>就變成了List只壳。接下來就可能需要生成一些橋接方法(bridge method),這是由于擦除了類型之后的類可能缺少某些必須的方法暑塑。比如考慮下面的代碼:

class MyString implements Comparable<String> {
    @Override
    public int compareTo(String str) {
        return 0;
    }
}

當(dāng)類型信息被擦除之后吼句,上述類的聲明變成了class MyString implements Comparable。但是這樣的話事格,類MyString就會(huì)有編譯錯(cuò)誤命辖,因?yàn)闆]有實(shí)現(xiàn)接口Comparable聲明的int compareTo(Object)方法,這個(gè)時(shí)候就由編譯器來動(dòng)態(tài)生成這個(gè)方法分蓖。

2.6.2 通配符與上下界

在使用泛型類的時(shí)候尔艇,既可以指定一個(gè)具體的類型,也可以用通配符?來表示未知類型么鹤,如List<?>就聲明了List中包含的元素類型是未知的终娃。通配符所代表的其實(shí)是一組類型,但具體的類型是未知的蒸甜。通配符分為三類:無界通配符棠耕、上界通配符和下界通配符余佛。通配符本身比較復(fù)雜,我們會(huì)以集合為代表窍荧,以元素的添加和獲取為例簡要說明其用法辉巡。

無界通配符

“?”表示無界通配符,List<?>表示:List中存儲(chǔ)的元素的類型是未知的蕊退。

  1. 添加元素郊楣,使用無界通配符時(shí),由于類型不確定的(可以是任何類型)瓤荔,不可以向List<?>添加任何元素(除了null)净蚤,因?yàn)槿绻薙tring的話,往里面添加Integer顯然是錯(cuò)誤的输硝。那為什么不能添加Object引用今瀑,是因?yàn)镺bject引用可以指向子類實(shí)例,編譯期是無法獲知其具體類型点把,所以為了類型安全橘荠,其不可以添加任何元素(除了null)。
  2. 獲取元素郎逃,List<?>中的元素只可以使用Object來引用哥童,因?yàn)槠湓乜隙ㄊ荗bject及其子類引用。

事實(shí)上無界通配符通常會(huì)用在以下兩種情況:

  1. 在業(yè)務(wù)邏輯與泛型類型無關(guān)衣厘,如List.size和List.clean等如蚜。實(shí)際上,最常用的就是Class<?>影暴,因?yàn)镃lass<T>并沒有依賴于T错邦。
  2. 當(dāng)方法參數(shù)是原始的Object類型,如下:
public static void printList(List<Object> list) {
    for (Object elem : list)
        System.out.println(elem + "");
}

//使用泛型類替換
public static void printList(List<?> list) {
    for (Object elem: list)
        System.out.print(elem + "");
}

這樣就可以兼容更多的輸出型宙,而不單純是List<Object>撬呢,如下:

List<Integer> li = Arrays.asList(1, 2, 3);
List<String>  ls = Arrays.asList("one", "two", "three");
printList(li);
printList(ls);

上界通配符

“? extends Animal”表示通配符的上界是Animal,即“? extends Animal”可以代表Animal及其子類妆兑,不能代表Animal父類魂拦。
首先要闡明一點(diǎn),上界通配符和下界通配符更多的是為了解決泛型不協(xié)變的問題搁嗓。

  1. 添加元素芯勘,使用上界通配符時(shí),其類型仍然是不確定的(會(huì)是某個(gè)類型及其子類型)腺逛,所以仍然不可以向List<荷愕? extends Animal>添加任何元素(null除外),因?yàn)槿绻薈at的話,往里面添加Dog顯然是錯(cuò)誤的安疗,同樣不可以添加Object引用抛杨。
  2. 獲取元素,List<? extends Animal>中的元素可以用Animal來引用的荐类,因?yàn)槠湓乜隙ㄊ茿nimal及其子類引用怖现。

而且引入了上界之后,在使用類型的時(shí)候就可以使用上界類中定義的方法玉罐,因?yàn)槠渲性乜隙ㄊ茿nimal類或其子類成員引用屈嗤。

下界通配符

“? super Animal”表示通配符的下界是Animal,即“? super Animal”可以代表Animal及其父類厌小,不能代表Animal子類恢共。

  1. 添加元素战秋,使用下界通配符時(shí)璧亚,雖然類型仍然是不確定的(會(huì)是某個(gè)類型及其父類),但是此時(shí)可以向List<脂信? super Animal>添加Animal或者其子類型Dog元素癣蟋,因?yàn)槿绻薃nimal的話,往里面添加Dog顯然是可以的狰闪,子類型可以替換父類型疯搅。
  2. 獲取元素,List<? super Animal>中的元素只可以用Object來引用的埋泵,“? super Animal”可以代表Animal及其父類幔欧,所以只能通過Object來進(jìn)行應(yīng)用。

總結(jié)

PECS(Producer Extends Consumer Super)原則:頻繁往外讀取內(nèi)容的丽声,適合用上界Extends礁蔗;經(jīng)常往里插入的,適合用下界Super雁社。

通配符是實(shí)參浴井,通配符這些看起來很奇怪的特性原因在于:編譯器要保證類型安全,Object類型是所有類型的祖宗類型霉撵,而父類引用是可以引用子類對(duì)象的磺浙。

2.6.3 類型系統(tǒng)

在Java中,大家比較熟悉的是通過繼承機(jī)制而產(chǎn)生的類型體系結(jié)構(gòu)徒坡,比如String繼承自O(shè)bject撕氧。根據(jù)Liskov替換原則,子類是可以替換父類的喇完。當(dāng)需要Object類的引用的時(shí)候伦泥,如果傳入一個(gè)String對(duì)象是沒有任何問題的。但是反過來的話,即用父類的引用替換子類引用的時(shí)候奄喂,就需要進(jìn)行強(qiáng)制類型轉(zhuǎn)換铐殃。這種自動(dòng)的子類替換父類的類型轉(zhuǎn)換機(jī)制,對(duì)于數(shù)組也是適用的(前面說過數(shù)組是協(xié)變的)跨新,String[]可以替換Object[]富腊。

而泛型的引入,對(duì)于這個(gè)類型系統(tǒng)產(chǎn)生了一定的影響域帐,正如前面提到的List<String>是不能替換掉List<Object>的赘被。引入泛型之后的類型系統(tǒng)增加了兩個(gè)維度:一個(gè)是類型參數(shù)自身的繼承體系結(jié)構(gòu),另外一個(gè)是泛型類或接口的繼承體系結(jié)構(gòu)肖揣。前者是指對(duì)于List<String>和List<Object>這樣的情況民假,類型參數(shù)String是繼承自O(shè)bject的。而后者是指List接口繼承自Collection接口龙优。對(duì)于這個(gè)類型系統(tǒng)羊异,有如下的一些規(guī)則:

  1. 相同類型參數(shù)的泛型類的關(guān)系取決于泛型類自身的繼承體系結(jié)構(gòu),即List<String>是Collection<String>的子類型彤断,List<String>可以替換Collection<String>野舶,這種情況也適用于帶有上下界的類型聲明。
  2. 當(dāng)泛型類的類型聲明中使用了通配符的時(shí)候宰衙,其子類型可以在兩個(gè)維度上分別展開平道。如對(duì)Collection<? extends Number>來說,其子類型可以在Collection這個(gè)維度上展開供炼,即List<? extends Number>和Set<? extends Number>等一屋;也可以在Number這個(gè)層次上展開,即Collection<Double>和Collection<Integer>等袋哼。如此循環(huán)下去冀墨,ArrayList<Long>和HashSet<Double>等也都算是Collection<? extends Number>的子類型。
  3. 如果泛型類中包含多個(gè)類型參數(shù)先嬉,則對(duì)于每個(gè)類型參數(shù)分別應(yīng)用上面的規(guī)則轧苫。

2.6.4 開發(fā)自己的泛型類

泛型類與一般的Java類基本相同,只是在類和接口定義上多出來了用<>聲明的類型參數(shù)疫蔓。一個(gè)類可以有多個(gè)類型參數(shù)含懊,如MyClass<X, Y, Z>。每個(gè)類型參數(shù)在聲明的時(shí)候可以指定上界衅胀。所聲明的類型參數(shù)在Java類中可以像一般的類型一樣作為方法的參數(shù)和返回值岔乔,或是作為域和局部變量的類型。但是由于類型擦除機(jī)制滚躯,類型參
數(shù)并不能用來創(chuàng)建對(duì)象或是作為靜態(tài)變量的類型雏门『俑瑁考慮下面的泛型類中的正確和錯(cuò)誤的用法。

class ClassTest<X extends Number, Y, Z> {
    private X x; //正確用法
    private static Y y; //編譯錯(cuò)誤,不能用在靜態(tài)變量中
    public X getFirst() { //正確用法
        return x;
    }
    public void wrong() {
        Z z = new Z(); //編譯錯(cuò)誤,不能創(chuàng)建對(duì)象
    }
}

2.6.5 最佳實(shí)踐

在使用泛型的時(shí)候可以遵循一些基本的原則,從而避免一些常見的問題茁影。

  1. 在代碼中避免泛型類和原始類型的混用宙帝。比如List<String>和List不應(yīng)該共同使用,這樣會(huì)產(chǎn)生一些編譯器警告和潛在的運(yùn)行時(shí)異常募闲。
  2. 在使用帶通配符的泛型類的時(shí)候步脓,需要明確通配符所代表的一組類型的概念。由于具體的類型是未知的浩螺,很多操作是不允許的靴患。
  3. 泛型類最好不要同數(shù)組一塊使用。你只能創(chuàng)建new List<?>[10]這樣的數(shù)組要出,無法創(chuàng)建new List<String>[10]這樣的鸳君。這限制了數(shù)組的使用能力,而且會(huì)帶來很多費(fèi)解的問題患蹂。因此當(dāng)需要類似數(shù)組的功能時(shí)候或颊,使用集合類即可。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末况脆,一起剝皮案震驚了整個(gè)濱河市饭宾,隨后出現(xiàn)的幾起案子批糟,更是在濱河造成了極大的恐慌格了,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件徽鼎,死亡現(xiàn)場(chǎng)離奇詭異盛末,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)否淤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門悄但,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人石抡,你說我怎么就攤上這事檐嚣。” “怎么了啰扛?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵嚎京,是天一觀的道長。 經(jīng)常有香客問我隐解,道長鞍帝,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任煞茫,我火速辦了婚禮帕涌,結(jié)果婚禮上摄凡,老公的妹妹穿的比我還像新娘。我一直安慰自己蚓曼,他們只是感情好亲澡,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著纫版,像睡著了一般谷扣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上捎琐,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天会涎,我揣著相機(jī)與錄音,去河邊找鬼瑞凑。 笑死末秃,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的籽御。 我是一名探鬼主播练慕,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼技掏!你這毒婦竟也來了铃将?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤哑梳,失蹤者是張志新(化名)和其女友劉穎劲阎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鸠真,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡悯仙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了吠卷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锡垄。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖祭隔,靈堂內(nèi)的尸體忽然破棺而出货岭,到底是詐尸還是另有隱情,我是刑警寧澤疾渴,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布千贯,位于F島的核電站,受9級(jí)特大地震影響程奠,放射性物質(zhì)發(fā)生泄漏丈牢。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一瞄沙、第九天 我趴在偏房一處隱蔽的房頂上張望己沛。 院中可真熱鬧慌核,春花似錦、人聲如沸申尼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽师幕。三九已至粟按,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間霹粥,已是汗流浹背灭将。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留后控,地道東北人庙曙。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像浩淘,于是被迫代替她去往敵國和親捌朴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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

  • 一张抄、泛型簡介 1.引入泛型的目的 了解引入泛型的動(dòng)機(jī)砂蔽,就先從語法糖開始了解。 語法糖 語法糖(Syntactic ...
    Ruheng閱讀 4,405評(píng)論 2 50
  • 泛型 泛型由來 泛型字面意思不知道是什么類型署惯,但又好像什么類型都是左驾。看前面用到的集合都有泛型的影子泽台。 以Array...
    向日花開閱讀 2,188評(píng)論 2 6
  • 一什荣、定義 Java泛型是JDK5中引入的新特性,提供在編譯時(shí)類型安全監(jiān)測(cè)機(jī)制怀酷,本質(zhì)是參數(shù)化類型(即所操作的數(shù)據(jù)類型...
    MrTrying閱讀 1,177評(píng)論 0 2
  • 一、引入泛型機(jī)制的原因 假如我們想要實(shí)現(xiàn)一個(gè)String數(shù)組嗜闻,并且要求它可以動(dòng)態(tài)改變大小蜕依,這時(shí)我們都會(huì)想到用Arr...
    Q南南南Q閱讀 532評(píng)論 0 1
  • 一不小心翠肘,中國的新年都過完了檐束,而今天也迎來了第一個(gè)上班的日子,雖然束倍,地鐵上人并不多... 在看艾力的《你一年的87...
    冰淇淋在路上閱讀 879評(píng)論 6 5