java進(jìn)階-泛型

1、什么是泛型

所謂泛型珊蟀,就是允許在定義類菊值、接口、方法時使用類型形參育灸,這個類型形參將在聲明變量腻窒、創(chuàng)建對象、調(diào)用方法時動態(tài)的指定磅崭。

2儿子、為什么要使用泛型

先看下這個例子:

    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("字符串");
        list.add(122);

        for (int i = 0; i <list.size() ; i++) {
            String str = (String) list.get(i);
            System.out.println(str);
        }
    }

我們運(yùn)行上面的代碼,就會拋出如下的異常绽诚。

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

我們知道ArrayList可以存放任意對象類型典徊,我們先向list中存放了一個String類型的數(shù)據(jù)杭煎,接著又存放了一個Integer類型的數(shù)據(jù)恩够,在使用的時候就拋出了異常。為了解決類似這樣的問題羡铲,泛型應(yīng)運(yùn)而出蜂桶。

對于上面的例子的例子做如下改造:

List<String> list = new ArrayList();
list.add("字符串");
list.add(122);  // add(java.long.String) in list connot be applied to (int)

當(dāng)我們使用泛型后,再存放Integer類型的數(shù)據(jù)也切,就會直接報(bào)錯扑媚,這就是再編譯時檢測,而上面的是在運(yùn)行時才去檢測的雷恃,這樣就能很大程度的解決程序員犯錯了疆股。

與非泛型代碼相比,使用泛型的代碼具有許多優(yōu)點(diǎn):

  • 在編譯時進(jìn)行更強(qiáng)的類型檢查倒槐。Java編譯器將強(qiáng)類型檢查應(yīng)用于泛型代碼旬痹,并在代碼違反類型安全時發(fā)出錯誤。修復(fù)編譯時錯誤比修復(fù)運(yùn)行時錯誤更容易讨越,運(yùn)行時錯誤很難找到两残。
  • 消除類型轉(zhuǎn)換。 當(dāng)使用的時候不需要強(qiáng)制轉(zhuǎn)換類型把跨。
  • 使程序員能夠?qū)崿F(xiàn)泛型算法人弓。 通過使用泛型,程序員可以實(shí)現(xiàn)泛型算法着逐,這些算法可以處理不同類型的集合崔赌,可以自定義湿痢,并且類型安全且易于閱讀分飞。

3、泛型的使用

泛型有三種使用方式,分別為:泛型類妄辩、泛型接口、泛型方法郊楣。

3.1操刀、泛型類

泛型類型用于類的定義中,就稱之為泛型類吩翻,泛型類的基本寫法如下:

public class Generic<T> {}
public class Generic<T1,T2,...,Tn> {}

在類名之后兜看,類型參數(shù)部分由尖括號<>分隔。它指定了類型參數(shù)(也稱為類型變量)T狭瞎,當(dāng)然了也可以定義多個參數(shù)细移。
下面看一個例子:

/**
 * 在實(shí)例化泛型類時,必須指定T的具體類型
 * @param <T> 此處T可以隨便寫為任意標(biāo)識熊锭,常見的如T弧轧、E、K碗殷、V等形式的參數(shù)常用于表示泛型
 */
public class Generic<T> {
    //key這個成員變量的類型為T,T的類型由外部指定
    private T key;

    //泛型構(gòu)造方法形參key的類型也為T精绎,T的類型由外部指定
    //這里需要注意的是構(gòu)造器名是Generic ,而不是Generic<T>锌妻。
    public Generic(T key) {
        this.key = key;
    }
    
    //泛型方法getKey的返回值類型為T代乃,T的類型由外部指定
    public T getKey() {
        return key;
    }
}

運(yùn)行下面代碼:

Generic<String> genericStr = new Generic<>("測試泛型類");
Generic<Integer> genericInt = new Generic<>(100);
System.out.println("泛型測試---》"+genericStr.getKey());
System.out.println("泛型測試---》"+genericInt.getKey());

// 運(yùn)行結(jié)果如下:
// 泛型測試---》測試泛型類
// 泛型測試---》100

對于多種類型的參數(shù)其實(shí)是一樣的做法,比如我們熟悉的HashMap就是多種類型參數(shù):

//HashMap的源碼
public class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, Cloneable, Serializable {...}
//HashMap的使用
Map<String,String> map = new HashMap<>();

3.2仿粹、泛型接口

其實(shí)泛型接口和泛型類的定義使用基本上是一樣的搁吓。模擬我們常用的list接口來演示下泛型接口

//定義一個泛型接口
public interface MyList<T> {
    public T add(T t);
}

當(dāng)實(shí)現(xiàn)泛型接口的類,未傳入泛型實(shí)參時吭历,與泛型類的定義相同堕仔,在聲明類的時候,需將泛型的聲明也一起加到類中

public class MyArrayList<T> implements MyList<T> {
    @Override
    public T add(T t) {
        return t;
    }

    public static void main(String[] args) {
        MyList<String> list = new MyArrayList<>();
        list.add("aaaa");
        MyList<Integer> list1 = new MyArrayList<>();
        list1.add(0);
    }
}

在實(shí)現(xiàn)類實(shí)現(xiàn)泛型接口時晌区,如已將泛型類型傳入實(shí)參類型摩骨,則所有使用泛型的地方都要替換成傳入的實(shí)參類型。

public class MyArrayList implements MyList<String> {
    @Override
    public String add(String s) {
        return s;
    }
}

3.3契讲、泛型方法

泛型方法仿吞,是在調(diào)用方法的時候指明泛型的具體類型〖衿基本寫法如下:

/**
 * @param <T>  這個<T>非常重要唤冈,可以理解為聲明此方法為泛型方法。
 *           只有聲明了<T>的方法才是泛型方法银伟。泛型類中的使用了泛型的成員方法并不是泛型方法你虹。
 */
public <T> void Generic(T t) {}

下面看下具體例子看下泛型方法的實(shí)現(xiàn):

public class TestGeneric {

    public <T> void getKey(T t) {
        System.out.println("測試泛型方法---》" +t);
    }

    public static void main(String[] args) {
        TestGeneric testGeneric = new TestGeneric();
        testGeneric.getKey("字符串");
        testGeneric.getKey(0);
    }
}

//輸出結(jié)果
//測試泛型方法---》字符串
//測試泛型方法---》0

3.4绘搞、泛型通配符

稱為通配符的問號(?)表示未知類型。通配符可以在多種情況下使用:作為參數(shù)傅物,字段或局部變量的類型夯辖;有時作為返回類。通配符從不用作泛型方法調(diào)用董饰,泛型類實(shí)例創(chuàng)建或超類型的類型參數(shù)蒿褂。下面詳細(xì)地討論通配符,包括上界通配符卒暂,下界通配符和通配符捕獲啄栓。

下面我們看一個例子:

    List<?> list = new ArrayList<String>();
    //我們可以正常獲取list中的元素,不會引起編譯錯誤
    Object object = list.get(0);
    //調(diào)用add方法會引起編譯錯誤
    list.add("字符串");

這種帶通配符的 List僅表示它是各種泛型List的父類也祠,并不能把元素添加到其中昙楚,否則會引起編譯錯誤。因?yàn)槌绦驘o法確定list集合中的元素類型诈嘿,所以不能向其中添加對象堪旧,而程序第哦啊用get()方法來返回List<?>集合指定索引處的元素,其返回值是一個未知類型奖亚,但肯定是一個Object淳梦,因此返回值賦給一個Object類型的變量是可以的。

3.4.1遂蛀、上界通配符

為泛型添加上邊界谭跨,即傳入的類型實(shí)參必須是指定類型的子類型干厚,要聲明上界通配符李滴,請使用通配符(?),然后使用extends關(guān)鍵字

public static void main(String[] args) {
    List<Integer> integers = new ArrayList<>();
    integers.add(1);
    List<Double> doubles = new ArrayList<>();
    doubles.add(2.0d);
    List<String> strings = new ArrayList<>();
    strings.add("通配符上界");

    print(integers);
    print(doubles);
    //編譯直接報(bào)錯
    //print(strings);
}
public static void print(List<? extends Number> list) {
    for (int i = 0; i < list.size(); i++) {
        System.out.println(list.get(i));
    }
}
3.4.2蛮瞄、下界通配符

通配符將未知類型限制為特定類型,下界通配符表示為通配符(?)所坯,后跟`super關(guān)鍵字

    public static void main(String[] args) {
        List<Integer> integers = new ArrayList<>();
        integers.add(0);
        List<Number> numbers = new ArrayList<>();
        numbers.add(123);
        numbers.add(10.0d);
        List<Double> doubles = new ArrayList<>();
        doubles.add(0.2d);
        print(integers);
        print(numbers);
        //編譯報(bào)錯
        //print(doubles);
    }

    public static void print(List<? super Integer> list) {
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }

上面我們可以看到定義了下界Integer后,我們就無法在使用Double類型了挂捅,只能使用Integer類型芹助、Number類型、Object類型了闲先。

4状土、泛型總結(jié)

使用泛型的時候,要考慮泛型的限制:

  • 無法實(shí)例化具有基本類型的泛型類型
  • 無法創(chuàng)建類型參數(shù)的實(shí)例
  • 無法聲明類型為類型參數(shù)的靜態(tài)字段
  • 無法創(chuàng)建參數(shù)化類型的數(shù)組
  • 無法創(chuàng)建捕獲或拋出參數(shù)化類型的對象
  • 無法重載每個重載的形式參數(shù)類型都擦除為相同原始(raw)類型的方法伺糠。
  • 無法將Castsinstanceof與參數(shù)化類型一起使用

泛型蒙谓,我們用到最多的可能就是集合了。在實(shí)際開發(fā)中训桶,我們合理的使用泛型可以簡化我們的開發(fā)累驮,提高為我們的開發(fā)效率酣倾。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市谤专,隨后出現(xiàn)的幾起案子躁锡,更是在濱河造成了極大的恐慌,老刑警劉巖置侍,帶你破解...
    沈念sama閱讀 221,406評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件映之,死亡現(xiàn)場離奇詭異,居然都是意外死亡蜡坊,警方通過查閱死者的電腦和手機(jī)惕医,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來算色,“玉大人抬伺,你說我怎么就攤上這事≡置危” “怎么了峡钓?”我有些...
    開封第一講書人閱讀 167,815評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長若河。 經(jīng)常有香客問我能岩,道長,這世上最難降的妖魔是什么萧福? 我笑而不...
    開封第一講書人閱讀 59,537評論 1 296
  • 正文 為了忘掉前任拉鹃,我火速辦了婚禮,結(jié)果婚禮上鲫忍,老公的妹妹穿的比我還像新娘膏燕。我一直安慰自己,他們只是感情好悟民,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,536評論 6 397
  • 文/花漫 我一把揭開白布坝辫。 她就那樣靜靜地躺著,像睡著了一般射亏。 火紅的嫁衣襯著肌膚如雪近忙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,184評論 1 308
  • 那天智润,我揣著相機(jī)與錄音及舍,去河邊找鬼。 笑死窟绷,一個胖子當(dāng)著我的面吹牛锯玛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播钾麸,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼更振,長吁一口氣:“原來是場噩夢啊……” “哼炕桨!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起肯腕,我...
    開封第一講書人閱讀 39,668評論 0 276
  • 序言:老撾萬榮一對情侶失蹤献宫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后实撒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體姊途,經(jīng)...
    沈念sama閱讀 46,212評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,299評論 3 340
  • 正文 我和宋清朗相戀三年知态,在試婚紗的時候發(fā)現(xiàn)自己被綠了捷兰。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,438評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡负敏,死狀恐怖贡茅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情其做,我是刑警寧澤顶考,帶...
    沈念sama閱讀 36,128評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站妖泄,受9級特大地震影響驹沿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蹈胡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,807評論 3 333
  • 文/蒙蒙 一渊季、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧罚渐,春花似錦却汉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,279評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至璧坟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間赎懦,已是汗流浹背雀鹃。 一陣腳步聲響...
    開封第一講書人閱讀 33,395評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留励两,地道東北人黎茎。 一個月前我還...
    沈念sama閱讀 48,827評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像当悔,于是被迫代替她去往敵國和親傅瞻。 傳聞我的和親對象是個殘疾皇子踢代,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,446評論 2 359

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