重溫Java泛型懂从,帶你更深入地理解它授段,更好的使用它!

1. 引言

jdk5.0中引入了Java泛型番甩,目的是減少錯(cuò)誤侵贵,并在類型上添加額外的抽象層。
本文將簡要介紹Java中的泛型缘薛、泛型背后的目標(biāo)以及如何使用泛型來提高代碼的質(zhì)量窍育。

2. 為什么要用泛型?

設(shè)想一個(gè)場景宴胧,我們希望用Java創(chuàng)建一個(gè)列表來存儲Integer漱抓;代碼可能會寫成這樣:

List list = new LinkedList();
list.add(new Integer(1)); 
Integer i = list.iterator().next();

令人驚訝的是,編譯器會提示最后一行恕齐。它不知道返回的數(shù)據(jù)類型是什么乞娄。因此,編譯器提示需要進(jìn)行顯式轉(zhuǎn)換:

Integer i = (Integer) list.iterator.next();

沒有任何約定可以保證列表的返回類型是整數(shù)。定義的列表可以包含任何對象补胚。我們只知道我們是通過檢查上下文來檢索列表的码耐。在查看類型時(shí),它只能保證它是一個(gè)Object溶其,因此需要顯式轉(zhuǎn)換來確保類型是安全的骚腥。

這種轉(zhuǎn)換可能會令人感到聒噪,我們明明知道這個(gè)列表中的數(shù)據(jù)類型是整數(shù)瓶逃。轉(zhuǎn)換的話束铭,也把我們的代碼搞得亂七八糟。如果程序員在顯式轉(zhuǎn)換中出錯(cuò)厢绝,則可能會導(dǎo)致拋出與 類型相關(guān)的運(yùn)行時(shí)錯(cuò)誤 契沫。

如果程序員能夠表達(dá)他們使用特定類型的意圖,并且編譯器能夠確保這種類型的正確性昔汉,那么這將更加容易懈万。

這就是泛型背后的核心思想。

我們將前面代碼段的第一行修改為:

List<Integer> list = new LinkedList<>();

通過添加包含類型的菱形運(yùn)算符<>靶病,我們將此列表的特化范圍縮小到 Integer 類型会通,即指定將保存在列表中的類型。編譯器可以在編譯時(shí)強(qiáng)制執(zhí)行該類型娄周。

在較小的程序中涕侈,這看起來像是一個(gè)微不足道的添加。但是在較大的程序中煤辨,這可以增加顯著的健壯性并使程序更易于閱讀裳涛。

3. 泛型方法

泛型方法是用單個(gè)方法聲明編寫的方法,可以用不同類型的參數(shù)調(diào)用众辨。編譯器將確保所用類型的正確性端三。以下是泛型方法的一些屬性:

  • 泛型方法在方法聲明的返回類型之前有一個(gè)類型參數(shù)(包裹類型的菱形運(yùn)算符)

  • 類型參數(shù)可以有界(邊界將在本文后面解釋)

  • 泛型方法可以具有不同的類型參數(shù),這些參數(shù)在方法簽名中用逗號分隔

  • 泛型方法的方法體與普通方法一樣

定義將數(shù)組轉(zhuǎn)換為列表的泛型方法的示例:

public <T> List<T> fromArrayToList(T[] a) {   
    return Arrays.stream(a).collect(Collectors.toList());
}

在前面的示例中泻轰,方法聲明中的<T>表示該方法將處理泛型類型 T技肩。即使方法返回的是void,也需要這樣做浮声。
如上所述虚婿,方法可以處理多個(gè)泛型類型,在這種情況下泳挥,所有泛型類型都必須添加到方法聲明中然痊,例如,如果我們要修改上面的方法來處理類型 T 和類型 G 屉符,應(yīng)該這樣寫:

public static <T, G> List<G> fromArrayToList(T[] a, Function<T, G> mapperFunction) {
    return Arrays.stream(a)
      .map(mapperFunction)
      .collect(Collectors.toList());
}

我們正在傳遞一個(gè)函數(shù)剧浸,該函數(shù)將具有T類型元素的數(shù)組轉(zhuǎn)換為包含G類型元素的列表锹引。例如,將 Integer 轉(zhuǎn)換為其 String 表示形式:

@Test
public void givenArrayOfIntegers_thanListOfStringReturnedOK() {
    Integer[] intArray = {1, 2, 3, 4, 5};
    List<String> stringList
      = Generics.fromArrayToList(intArray, Object::toString);
 
    assertThat(stringList, hasItems("1", "2", "3", "4", "5"));
}

Oracle建議使用大寫字母表示泛型類型唆香,并選擇更具描述性的字母來表示形式類型嫌变,例如在Java集合中,T 用于類型躬它,K 表示鍵腾啥,V 表示值。

3.1.泛型邊界

如前所述冯吓,類型參數(shù)可以是有界的。有界意味著“限制”组贺,我們可以限制方法可以接受的類型凸舵。

例如,可以指定一個(gè)方法接受一個(gè)類型及其所有子類(上限)或一個(gè)類型所有它的超類(下限)失尖。

要聲明上界類型啊奄,我們在類型后面使用關(guān)鍵字extends,后跟要使用的上限雹仿。例如:

public <T extends Number> List<T> fromArrayToList(T[] a) {
    ...
}

這里使用關(guān)鍵字extends表示類型 T 擴(kuò)展類的上限增热,或者實(shí)現(xiàn)接口的上限。

3.2. 多個(gè)邊界

類型還可以有多個(gè)上界胧辽,如下所示:

<T extends Number & Comparable>

如果 T 擴(kuò)展的類型之一是類(即Number),則必須將其放在邊界列表的第一位公黑。否則邑商,將導(dǎo)致編譯時(shí)錯(cuò)誤。

4. 使用通配符

通配符在Java中用問號“凡蚜?“ 表示人断,它們是用來指一種未知的類型。通配符在使用泛型時(shí)特別有用朝蜘,可以用作參數(shù)類型恶迈,但首先要考慮的是一個(gè)重要的注釋。

眾所周知谱醇,Object是所有Java類的超類型暇仲,但是,Object的集合不是任何集合的超類型副渴。(可能有點(diǎn)繞奈附,大家好好細(xì)品一下)

例如,List<Object>不是 List<String>的超類型煮剧,將List<Object>類型的變量賦值給List<String>類型的變量將導(dǎo)致編譯器錯(cuò)誤斥滤。

這是為了防止在將異構(gòu)類型添加到同一集合時(shí)可能發(fā)生的沖突将鸵。

相同的規(guī)則適用于類型及其子類型的任何集合∮悠模看看這個(gè)例子:

public static void paintAllBuildings(List<Building> buildings) {
    buildings.forEach(Building::paint);
}

如果我們設(shè)想一個(gè)子類型Building顶掉,實(shí)例House,那么我們不能將此方法與House列表一起使用挑胸,即使HouseBuilding的子類型一喘。如果需要將此方法與類型構(gòu)建及其所有子類型一起使用,則有界通配符可以實(shí)現(xiàn)以下功能:

public static void paintAllBuildings(List<? extends Building> buildings) {
    ...
}

現(xiàn)在嗜暴,這個(gè)方法可以處理Building類型及其所有子類型凸克。這稱為上界通配符,其中類型Building是上界闷沥。

通配符也可以使用下限指定萎战,其中未知類型必須是指定類型的超類型∮咛樱可以使用super關(guān)鍵字后跟特定類型來指定下限蚂维,例如,<? super T>表示未知類型路狮,它是 T(=T及其所有父類)的超類虫啥。

5. 類型擦除

泛型被添加到Java中以確保類型安全,并確保泛型不會在運(yùn)行時(shí)造成開銷奄妨,編譯器在編譯時(shí)對泛型應(yīng)用一個(gè)名為type erasure的進(jìn)程涂籽。

類型擦除刪除所有類型參數(shù),并將其替換為它們的邊界砸抛,如果類型參數(shù)是無界的评雌,則替換為Object。因此直焙,編譯后的字節(jié)碼只包含普通的類景东、接口和方法,從而確保不會生成新的類型奔誓。在編譯時(shí)對Object類型也應(yīng)用了正確的強(qiáng)制轉(zhuǎn)換斤吐。
以下是類型擦除的一個(gè)示例:

public <T> List<T> genericMethod(List<T> list) {
    return list.stream().collect(Collectors.toList());
}

使用類型擦除,無界類型T將替換為Object厨喂,如下所示:

// for illustration
public List<Object> withErasure(List<Object> list) {
    return list.stream().collect(Collectors.toList());
}
 
// which in practice results in
public List withErasure(List list) {
    return list.stream().collect(Collectors.toList());
}

如果類型是有界的和措,則在編譯時(shí)該類型將替換為綁定:

public <T extends Building> void genericMethod(T t) {
    ...
}

編譯后會發(fā)生變化:

public void genericMethod(Building t) {
    ...
}

6. 泛型和原始數(shù)據(jù)類型

Java中泛型的一個(gè)限制是類型參數(shù)不能是基本類型

例如,以下內(nèi)容無法編譯:

List<int> list = new ArrayList<>();
list.add(17);

為了理解原始數(shù)據(jù)類型為什么不起作用杯聚,只需記住 泛型是編譯時(shí)特性臼婆,這意味著類型將會被擦除,所有泛型類型都實(shí)現(xiàn)為 Object 類幌绍。
舉一個(gè)例子颁褂,讓我們看看列表的 add 方法:

List<Integer> list = new ArrayList<>();
list.add(17);

add 方法的聲明如下:

boolean add(E e);

并將被編譯為:

boolean add(Object e);

因此故响,類型參數(shù)必須可轉(zhuǎn)換為Object由于基本類型不繼承自 Object颁独,所以不能將它們用作類型參數(shù)
但是彩届,Java為它們提供了裝箱類型,以及自動裝箱和自動拆箱:

Integer a = 17;
int b = a;

因此誓酒,如果我們想創(chuàng)建一個(gè)可以保存整數(shù)的列表樟蠕,我們可以使用包裝器:

List<Integer> list = new ArrayList<>();
list.add(17);
int first = list.get(0);

編譯后的代碼相當(dāng)于:

List list = new ArrayList<>();
list.add(Integer.valueOf(17));
int first = ((Integer) list.get(0)).intValue();

Java的未來版本可能允許泛型使用原始數(shù)據(jù)類型。Valhalla 工程旨在改進(jìn)處理泛型的方式靠柑。其思想是實(shí)現(xiàn)JEP 218中描述的泛型專門化.

**7. 總結(jié) **

Java泛型是對Java語言的一個(gè)強(qiáng)大的補(bǔ)充寨辩,因?yàn)樗钩绦騿T的工作更容易,也更不容易出錯(cuò)歼冰。泛型在編譯時(shí)強(qiáng)制執(zhí)行類型正確性靡狞,并且,最重要的是隔嫡,能夠?qū)崿F(xiàn)泛型算法甸怕,而不會給我們的應(yīng)用程序帶來任何額外的開銷。

如果你覺得文章還不錯(cuò)腮恩,記得關(guān)注公眾號: 鍋外的大佬
劉一手的博客

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末梢杭,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子秸滴,更是在濱河造成了極大的恐慌武契,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缸榛,死亡現(xiàn)場離奇詭異吝羞,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)内颗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來敦腔,“玉大人均澳,你說我怎么就攤上這事》危” “怎么了找前?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長判族。 經(jīng)常有香客問我躺盛,道長,這世上最難降的妖魔是什么形帮? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任槽惫,我火速辦了婚禮周叮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘界斜。我一直安慰自己仿耽,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布各薇。 她就那樣靜靜地躺著项贺,像睡著了一般。 火紅的嫁衣襯著肌膚如雪峭判。 梳的紋絲不亂的頭發(fā)上开缎,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天,我揣著相機(jī)與錄音林螃,去河邊找鬼奕删。 笑死,一個(gè)胖子當(dāng)著我的面吹牛治宣,可吹牛的內(nèi)容都是我干的急侥。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼侮邀,長吁一口氣:“原來是場噩夢啊……” “哼坏怪!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起绊茧,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤铝宵,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后华畏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鹏秋,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年亡笑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了侣夷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,646評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡仑乌,死狀恐怖百拓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情晰甚,我是刑警寧澤衙传,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站厕九,受9級特大地震影響蓖捶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜扁远,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一俊鱼、第九天 我趴在偏房一處隱蔽的房頂上張望刻像。 院中可真熱鬧,春花似錦亭引、人聲如沸绎速。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽纹冤。三九已至,卻和暖如春购公,著一層夾襖步出監(jiān)牢的瞬間萌京,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工宏浩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留知残,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓比庄,卻偏偏與公主長得像求妹,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子佳窑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評論 2 348

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

  • 簡介 泛型的意思就是參數(shù)化類型制恍,通過使用參數(shù)化類型創(chuàng)建的接口、類神凑、方法净神,可以指定所操作的數(shù)據(jù)類型。比如:可以使用參...
    零度沸騰_yjz閱讀 3,306評論 1 15
  • 說來慚愧溉委,雖然平時(shí)經(jīng)常會使用到一些泛型類鹃唯,但是卻一直沒有深入地去了解過泛型機(jī)制。今天開始學(xué)習(xí)記錄泛型機(jī)制相關(guān)的知識...
    怡紅快綠閱讀 526評論 0 1
  • [TOC] 深入理解 Java 泛型 概述 泛型的本質(zhì)是參數(shù)化類型瓣喊,通常用于輸入?yún)?shù)坡慌、存儲類型不確定的場景。相比于...
    albon閱讀 5,303評論 0 7
  • 一、泛型的必要性 【1.1】沒有泛型之前 在說明為什么有泛型之前坑夯,我們先看一段代碼 這段代碼岖寞,現(xiàn)在已經(jīng)很少看到了。...
    Caijet閱讀 750評論 0 1
  • 黑色的海島上懸著一輪又大又圓的明月指巡,毫不嫌棄地把溫柔的月色照在這寸草不生的小島上。一個(gè)少年白衣白發(fā)隶垮,悠閑自如地倚坐...
    小水Vivian閱讀 3,102評論 1 5