秒懂Java泛型

版權(quán)申明】非商業(yè)目的附文章鏈接可自由轉(zhuǎn)載
博文地址:http://www.reibang.com/p/fb6f33cac3bf
出自:shusheng007

概述

什么是泛型枉证?為什么需要泛型贮尉?如何使用?是什么原理躺坟?如何改進(jìn)? 這基本上就是我們學(xué)習(xí)一項(xiàng)技術(shù)的正確套路和二,本文將按照以上順序展開(kāi)幅虑,由于水平有限,肯定會(huì)有不足之處盒刚,請(qǐng)多包含和指教腺劣。

什么是泛型

泛型的本質(zhì)是參數(shù)化類型,即給類型指定一個(gè)參數(shù),然后在使用時(shí)再指定此參數(shù)具體的值,那樣這個(gè)類型就可以在使用時(shí)決定了郁惜。這種參數(shù)類型可以用在類、接口和方法中趾断,分別被稱為泛型類、泛型接口吓懈、泛型方法歼冰。

為什么需要泛型

Java中引入泛型最主要的目的是將類型檢查工作提前到編譯時(shí)期靡狞,將類型強(qiáng)轉(zhuǎn)(cast)工作交給編譯器耻警,從而讓你在編譯時(shí)期就獲得類型轉(zhuǎn)換異常以及去掉源碼中的類型強(qiáng)轉(zhuǎn)代碼。例如

沒(méi)有泛型前:

private static void genericTest() {
    List arrayList = new ArrayList();
    arrayList.add("總有刁民想害朕");
    arrayList.add(7);

    for (int i = 0; i < arrayList.size(); i++) {
        Object item = arrayList.get(i);
        if (item instanceof String) {
            String str = (String) item;
            System.out.println("泛型測(cè)試 item = " + str);
        }else if (item instanceof Integer)
        {
            Integer inte = (Integer) item;
            System.out.println("泛型測(cè)試 item = " + inte);
        }
    }
}

如上代碼所示甸怕,在沒(méi)有泛型之前類型的檢查類型的強(qiáng)轉(zhuǎn)都必須由我們程序員自己負(fù)責(zé)甘穿,一旦我們犯了錯(cuò)(誰(shuí)還能不犯錯(cuò)?)梢杭,就是一個(gè)運(yùn)行時(shí)崩潰等著我們温兼。那時(shí)候我們就會(huì)抱怨了:你媽的狗逼編譯器,毛也檢查不出來(lái)武契,我把一個(gè)Integer 類型的對(duì)象強(qiáng)行轉(zhuǎn)換成String類型你在編譯的時(shí)候也不告訴我募判,害的老子程序運(yùn)行時(shí)崩潰了,這個(gè)月獎(jiǎng)金沒(méi)了咒唆!

有了泛型后:

 private static void genericTest2() {
     List<String> arrayList = new ArrayList<>();
     arrayList.add("總有刁民想害朕");
     arrayList.add(7); //..(參數(shù)不匹配:int 無(wú)法轉(zhuǎn)換為String)
     ...
 }

如上代碼届垫,編譯器在編譯時(shí)期即可完成類型檢查工作,并提出錯(cuò)誤(其實(shí)IDE在代碼編輯過(guò)程中已經(jīng)報(bào)紅了)全释。這次該編譯器發(fā)飆了:你丫是不是傻装处,這是一個(gè)只能放入String類型的列表,你放Int類型是幾個(gè)意思浸船?

泛型作用的對(duì)象

泛型有三種使用方式妄迁,分別為:泛型類寝蹈、泛型接口和泛型方法。

泛型類

在類的申明時(shí)指定參數(shù)登淘,即構(gòu)成了泛型類箫老,例如下面代碼中就指定T為類型參數(shù),那么在這個(gè)類里面就可以使用這個(gè)類型了形帮。例如申明T類型的變量name槽惫,申明T類型的形參param等操作。

public class Generic<T> {
    public T name;
    public Generic(T param){
        name=param;
    }
    public T m(){
        return name;
    }
}

那么在使用類時(shí)就可以傳入相應(yīng)的類型辩撑,構(gòu)建不同類型的實(shí)例界斜,如下面代碼分別傳入了String,Integer,Boolean3個(gè)類型:

 private static void genericClass()
 {
     Generic<String> str=new Generic<>("總有刁民想害朕");
     Generic<Integer> integer=new Generic<>(110);
     Generic<Boolean> b=new Generic<>(true);

     System.out.println("傳入類型:"+str.name+"  "+integer.name+"  "+b.name);
}

輸出結(jié)果為:傳入類型:總有刁民想害朕 110 true

如果沒(méi)有泛型,我們想要達(dá)到上面的效果需要定義三個(gè)類合冀,或者一個(gè)包含三個(gè)構(gòu)造函數(shù)各薇,三個(gè)取值方法的類。

泛型接口

泛型接口與泛型類的定義基本一致

public interface Generator<T> {
    public T produce();
}

泛型方法

這個(gè)相對(duì)來(lái)說(shuō)就比較復(fù)雜君躺,當(dāng)我首次接觸時(shí)也是一臉懵逼峭判,抓住特點(diǎn)后也就沒(méi)有那么難了。

public class Generic<T> {
    public T name;
    public  Generic(){}
    public Generic(T param){
        name=param;
    }
    public T m(){
        return name;
    }
    public <E> void m1(E e){ }
    public <T> T m2(T e){ }
}

重點(diǎn)看public <E> void m1(E e){ }這就是一個(gè)泛型方法棕叫,判斷一個(gè)方法是否是泛型方法關(guān)鍵看方法返回值前面有沒(méi)有使用<>標(biāo)記的類型林螃,有就是,沒(méi)有就不是俺泣。這個(gè)<>里面的類型參數(shù)就相當(dāng)于為這個(gè)方法聲明了一個(gè)類型疗认,這個(gè)類型可以在此方法的作用塊內(nèi)自由使用。
上面代碼中伏钠,m()方法不是泛型方法横漏,m1()m2()都是。值得注意的是m2()方法中聲明的類型T與類申明里面的那個(gè)參數(shù)T不是一個(gè)熟掂,也可以說(shuō)方法中的T隱藏了類型中的T缎浇。下面代碼中類里面的T傳入的是String類型,而方法中的T傳入的是Integer類型赴肚。

Generic<String> str=new Generic<>("總有刁民想害朕");
str.m2(123);

泛型的使用方法

如何繼承一個(gè)泛型類

如果不傳入具體的類型素跺,則子類也需要指定類型參數(shù),代碼如下:

class Son<T> extends Generic<T>{}

如果傳入具體參數(shù)誉券,則子類不需要指定類型參數(shù)

class Son extends Generic<String>{}

如何實(shí)現(xiàn)一個(gè)泛型接口

class ImageGenerator<T> implements Generator<T>{
    @Override
    public T produce() {
        return null;
    }
}

如何調(diào)用一個(gè)泛型方法

和調(diào)用普通方法一致指厌,不論是實(shí)例方法還是靜態(tài)方法。

通配符横朋?

仑乌?代表任意類型,例如有如下函數(shù):

public void m3(List<?>list){
    for (Object o : list) {
        System.out.println(o);
    }
}

其參數(shù)類型是晰甚,那么我們調(diào)用的時(shí)候就可以傳入任意類型的List,如下

str.m3(Arrays.asList(1,2,3));
str.m3(Arrays.asList("總有刁民","想害","朕"));

但是說(shuō)實(shí)話衙传,單獨(dú)一個(gè)意義不大厕九,因?yàn)榇蠹铱梢钥吹奖痛罚瑥募现蝎@取到的對(duì)象的類型是Object 類型的,也就只有那幾個(gè)默認(rèn)方法可調(diào)用扁远,幾乎沒(méi)什么用俊鱼。如果你想要使用傳入的類型那就需要強(qiáng)制類型轉(zhuǎn)換,這是我們接受不了的畅买,不然使用泛型干毛并闲。其真正強(qiáng)大之處是可以通過(guò)設(shè)置其上下限達(dá)到類型的靈活使用,且看下面分解重點(diǎn)內(nèi)容谷羞。

通配符上界

通配符上界使用<? extends T>的格式帝火,意思是需要一個(gè)T類型或者T類型的子類,一般T類型都是一個(gè)具體的類型湃缎,例如下面的代碼犀填。

public void printIntValue(List<? extends Number> list) {  
    for (Number number : list) {  
        System.out.print(number.intValue()+" ");   
    }  
}

這個(gè)意義就非凡了,無(wú)論傳入的是何種類型的集合嗓违,我們都可以使用其父類的方法統(tǒng)一處理九巡。

通配符下界

通配符下界使用<? super T>的格式,意思是需要一個(gè)T類型或者T類型的父類蹂季,一般T類型都是一個(gè)具體的類型冕广,例如下面的代碼。

public void fillNumberList(List<? super Number> list) {  
    list.add(new Integer(0));  
    list.add(new Float(1.0));  
} 

至于什么時(shí)候使用通配符上界乏盐,什么時(shí)候使用下界佳窑,在《Effective Java》中有很好的指導(dǎo)意見(jiàn):遵循PECS原則制恍,即producer-extends,consumer-super. 換句話說(shuō)父能,如果參數(shù)化類型表示一個(gè)生產(chǎn)者,就使用 <? extends T>净神;如果參數(shù)化類型表示一個(gè)消費(fèi)者何吝,就使用<? super T>

泛型在靜態(tài)方法中的問(wèn)題

泛型類中的靜態(tài)方法和靜態(tài)變量不可以使用泛型類所聲明的泛型類型參數(shù)鹃唯,例如下面的代碼編譯失敗

public class Test<T> {      
    public static T one;   //編譯錯(cuò)誤      
    public static  T show(T one){ //編譯錯(cuò)誤      
        return null;      
    }      
} 

因?yàn)殪o態(tài)方法和靜態(tài)變量屬于類所有爱榕,而泛型類中的泛型參數(shù)的實(shí)例化是在創(chuàng)建泛型類型對(duì)象時(shí)指定的,所以如果不創(chuàng)建對(duì)象坡慌,根本無(wú)法確定參數(shù)類型黔酥。但是靜態(tài)泛型方法是可以使用的,我們前面說(shuō)過(guò),泛型方法里面的那個(gè)類型和泛型類那個(gè)類型完全是兩回事跪者。

 public static <T>T show(T one){   
     return null;      
 } 

Java泛型原理解析

為什么人們會(huì)說(shuō)Java的泛型是偽泛型呢棵帽,就是因?yàn)镴ava在編譯時(shí)擦除了所有的泛型信息,所以Java根本不會(huì)產(chǎn)生新的類型到字節(jié)碼或者機(jī)器碼中渣玲,所有的泛型類型最終都將是一種原始類型逗概,那樣在Java運(yùn)行時(shí)根本就獲取不到泛型信息。

擦除

Java編譯器編譯泛型的步驟:
1.檢查泛型的類型 忘衍,獲得目標(biāo)類型
2.擦除類型變量逾苫,并替換為限定類型(T為無(wú)限定的類型變量,用Object替換)
3.調(diào)用相關(guān)函數(shù)枚钓,并將結(jié)果強(qiáng)制轉(zhuǎn)換為目標(biāo)類型铅搓。

 ArrayList<String> arrayString=new ArrayList<String>();     
 ArrayList<Integer> arrayInteger=new ArrayList<Integer>();     
 System.out.println(arrayString.getClass()==arrayInteger.getClass()); 

上面代碼輸入結(jié)果為 true,可見(jiàn)通過(guò)運(yùn)行時(shí)獲取的類信息是完全一致的搀捷,泛型類型被擦除了狸吞!

如何擦除:
當(dāng)擦除泛型類型后,留下的就只有原始類型了指煎,例如上面的代碼蹋偏,原始類型就是ArrayList。擦除類型變量至壤,并替換為限定類型(T為無(wú)限定的類型變量威始,用Object替換),如下所示

擦除之前:

//泛型類型  
class Pair<T> {    
    private T value;    
    public T getValue() {    
        return value;    
    }    
    public void setValue(T  value) {    
        this.value = value;    
    }    
} 

擦除之后:

//原始類型  
class Pair {    
    private Object value;    
    public Object getValue() {    
        return value;    
    }    
    public void setValue(Object  value) {    
        this.value = value;    
    }    
}  

因?yàn)樵?code>Pair<T>中像街,T是一個(gè)無(wú)限定的類型變量黎棠,所以用Object替換。如果是Pair<T extends Number>镰绎,擦除后脓斩,類型變量用Number類型替換。

與其他語(yǔ)言相比較

相比較Java,其模仿者C#在泛型方面無(wú)疑做的更好畴栖,其是真泛型随静。

C#泛型類在編譯時(shí),先生成中間代碼IL吗讶,通用類型T只是一個(gè)占位符燎猛。在實(shí)例化類時(shí),根據(jù)用戶指定的數(shù)據(jù)類型代替T并由即時(shí)編譯器(JIT)生成本地代碼照皆,這個(gè)本地代碼中已經(jīng)使用了實(shí)際的數(shù)據(jù)類型重绷,等同于用實(shí)際類型寫(xiě)的類,所以不同的封閉類的本地代碼是不一樣的膜毁。其可以在運(yùn)行時(shí)通過(guò)反射獲得泛型信息昭卓,而且C#的泛型大大提高了代碼的執(zhí)行效率愤钾。

那么Java為什么不采用類似C#的實(shí)現(xiàn)方式呢,答案是:要向下兼容候醒!兼容害死人啊绰垂。關(guān)于C#與Java的泛型比較,可以查看這篇文章:Comparing Java and C# Generics

總結(jié)

本文追求的目標(biāo)就是讓人看了就懂火焰,包括自己劲装,希望達(dá)到了。怕什么真理無(wú)窮昌简,進(jìn)一寸有一寸的歡喜占业。

參考文章 https://blog.csdn.net/sunxianghuang/article/details/51982979#commentsedit

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市纯赎,隨后出現(xiàn)的幾起案子谦疾,更是在濱河造成了極大的恐慌,老刑警劉巖犬金,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件念恍,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡晚顷,警方通過(guò)查閱死者的電腦和手機(jī)峰伙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)该默,“玉大人瞳氓,你說(shuō)我怎么就攤上這事∷ㄐ洌” “怎么了匣摘?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)裹刮。 經(jīng)常有香客問(wèn)我音榜,道長(zhǎng),這世上最難降的妖魔是什么捧弃? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任赠叼,我火速辦了婚禮,結(jié)果婚禮上塔橡,老公的妹妹穿的比我還像新娘梅割。我一直安慰自己霜第,他們只是感情好葛家,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著泌类,像睡著了一般癞谒。 火紅的嫁衣襯著肌膚如雪底燎。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,365評(píng)論 1 302
  • 那天弹砚,我揣著相機(jī)與錄音双仍,去河邊找鬼。 笑死桌吃,一個(gè)胖子當(dāng)著我的面吹牛朱沃,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播茅诱,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼逗物,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了瑟俭?” 一聲冷哼從身側(cè)響起翎卓,我...
    開(kāi)封第一講書(shū)人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎摆寄,沒(méi)想到半個(gè)月后失暴,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡微饥,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年逗扒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片欠橘。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡缴阎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出简软,到底是詐尸還是另有隱情蛮拔,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布痹升,位于F島的核電站建炫,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏疼蛾。R本人自食惡果不足惜肛跌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望察郁。 院中可真熱鬧衍慎,春花似錦、人聲如沸皮钠。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)麦轰。三九已至乔夯,卻和暖如春砖织,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背末荐。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工侧纯, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人甲脏。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓眶熬,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親块请。 傳聞我的和親對(duì)象是個(gè)殘疾皇子聋涨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354