摘要:可以看到,如果我們給泛型類(lèi)制定了上限,泛型擦除之后就會(huì)被替換成類(lèi)型的上限。相應(yīng)的,泛型類(lèi)中定義的方法的類(lèi)型也是如此。參考語(yǔ)言類(lèi)型擦除下界通配符和的區(qū)別
本篇博客主要介紹了Java類(lèi)型擦除的定義,詳細(xì)的介紹了類(lèi)型擦除在Java中所出現(xiàn)的場(chǎng)景。
1. 什么是類(lèi)型擦除為了讓你們快速的對(duì)類(lèi)型擦除有一個(gè)印象,首先舉一個(gè)很簡(jiǎn)單也很經(jīng)典的例子。
// 指定泛型為String Listlist1 = new ArrayList<>(); // 指定泛型為Integer List list2 = new ArrayList<>(); System.out.println(list1.getClass() == list2.getClass()); // true
上面的判斷結(jié)果是true。代表了兩個(gè)傳入了不同泛型的List最終都編譯成了ArrayList,成為了同一種類(lèi)型,原來(lái)的泛型參數(shù)String和Integer被擦除掉了。這就是類(lèi)型擦除的一個(gè)典型的例子。
而如果我們說(shuō)到類(lèi)型擦除為什么會(huì)出現(xiàn),我們就必須要了解泛型。
2. 泛型 2.1. 泛型的定義隨著2004年9月30日,工程代號(hào)為T(mén)iger的JDK 1.5發(fā)布,泛型從此與大家見(jiàn)面。JDK 1.5在Java語(yǔ)法的易用性上作出了非常大的改進(jìn)。除了泛型,同版本加入的還有自動(dòng)裝箱、動(dòng)態(tài)注解、枚舉、可變長(zhǎng)參數(shù)、foreach循環(huán)等等。
而在1.5之前的版本中,為了讓Java的類(lèi)具有通用性,參數(shù)類(lèi)型和返回類(lèi)型通常都設(shè)置為Object,可見(jiàn),如果需要不用的類(lèi)型,就需要在相應(yīng)的地方,對(duì)其進(jìn)行強(qiáng)制轉(zhuǎn)換,程序才可以正常運(yùn)行,十分麻煩,稍不注意就會(huì)出錯(cuò)。
泛型的本質(zhì)就是參數(shù)化類(lèi)型。也就是,將一個(gè)數(shù)據(jù)類(lèi)型指定為參數(shù)。引入泛型有什么好處呢?
泛型可以將JDK 1.5之前在運(yùn)行時(shí)才能發(fā)現(xiàn)的錯(cuò)誤,提前到編譯期。也就是說(shuō),泛型提供了編譯時(shí)類(lèi)型安全的檢測(cè)機(jī)制。例如,一個(gè)變量本來(lái)是Integer類(lèi)型,我們?cè)诖a中設(shè)置成了String,沒(méi)有使用泛型的時(shí)候只有在代碼運(yùn)行到這了,才會(huì)報(bào)錯(cuò)。
而引入泛型之后就不會(huì)出現(xiàn)這個(gè)問(wèn)題。這是因?yàn)橥ㄟ^(guò)泛型可以知道該參數(shù)的規(guī)定類(lèi)型,然后在編譯時(shí),判斷其類(lèi)型是否符合規(guī)定類(lèi)型。
泛型總共有三種使用方法,分別使用于類(lèi)、方法和接口。
3. 泛型的使用方法 3.1 泛型類(lèi) 3.1.1 定義泛型類(lèi)簡(jiǎn)單的泛型類(lèi)可以定義為如下。
public class Generic{ T data; public Generic(T data) { setData(data); } public T getData() { return data; } public void setData(T data) { this.data = data; } }
其中的T代表參數(shù)類(lèi)型,代表任何類(lèi)型。當(dāng)然,并不是一定要寫(xiě)成T,這只是大家約定俗成的習(xí)慣而已。有了上述的泛型類(lèi)之后我們就可以像如下的方式使用了。
3.1.2 使用泛型類(lèi)// 假設(shè)有這樣一個(gè)具體的類(lèi) public class Hello { private Integer id; private String name; private Integer age; private String email; } // 使用泛型類(lèi) Hello hello = new Hello(); Genericresult = new Generic<>(); resule.setData(hello); // 通過(guò)泛型類(lèi)獲取數(shù)據(jù) Hello data = result.getData();
當(dāng)然如果泛型類(lèi)不傳入指定的類(lèi)型的話(huà),泛型類(lèi)中的方法或者成員變量定義的類(lèi)型可以為任意類(lèi)型,如果打印result.getClass()的話(huà),會(huì)得到Generic。
3.2. 泛型方法 3.2.1 定義泛型方法首先我們看一下不帶返回值的泛型方法,可以定義為如下結(jié)構(gòu)。
// 定義不帶返回值的泛型方法 public3.2.2 調(diào)用泛型方法void genericMethod(T field) { System.out.println(field.getClass().toString()); } // 定義帶返回值的泛型方法 private T genericWithReturnMethod(T field) { System.out.println(field.getClass().toString()); return field; }
// 調(diào)用不帶返回值泛型方法 genericMethod("This is string"); // class java.lang.String genericMethod(56L); // class java.lang.Long // 調(diào)用帶返回值的泛型方法 String test = genericWithReturnMethod("TEST"); // TEST class java.lang.String
帶返回值的方法中,T就是當(dāng)前函數(shù)的返回類(lèi)型。
3.3. 泛型接口泛型接口定義如下
public interface genericInterface{ }
使用的方法與泛型類(lèi)類(lèi)似,這里就不再贅述。
4. 泛型通配符什么是泛型通配符?官方一點(diǎn)的解釋是
Type of unknown.
也就是無(wú)限定的通配符,可以代表任意類(lèi)型。用法也有三種,>, extends T>和 super T>。
既然已經(jīng)有了T這樣的代表任意類(lèi)型的通配符,為什么還需要這樣一個(gè)無(wú)限定的通配符呢?是因?yàn)槠渲饕鉀Q的問(wèn)題是泛型繼承帶來(lái)的問(wèn)題。
4.1. 泛型的繼承問(wèn)題首先來(lái)看一個(gè)例子
ListintegerList = new ArrayList<>(); List numberList = integerList;
我們知道,Integer是繼承自Number類(lèi)的。
public final class Integer extends Number implements Comparable{
....
}
那么上述的代碼能夠通過(guò)編譯嗎?肯定是不行的。Integer繼承自Number不代表List
在其他函數(shù)中,例如JavaScript中,一個(gè)函數(shù)的參數(shù)可以是任意的類(lèi)型,而不需要進(jìn)行任意的類(lèi)型轉(zhuǎn)換,所以這樣的函數(shù)在某些應(yīng)用場(chǎng)景下,就會(huì)具有很強(qiáng)的通用性。
而在Java這種強(qiáng)類(lèi)型語(yǔ)言中,一個(gè)函數(shù)的參數(shù)類(lèi)型是固定不變的。那如果想要在Java中實(shí)現(xiàn)類(lèi)似于JavaScript那樣的通用函數(shù)該怎么辦呢?這也就是為什么我們需要泛型的通配符。
假設(shè)我們有很多動(dòng)物的類(lèi), 例如Dog, Pig和Cat三個(gè)類(lèi),我們需要有一個(gè)通用的函數(shù)來(lái)計(jì)算動(dòng)物列表中的所有動(dòng)物的腿的總數(shù),如果在Java中,要怎么做呢?
可能會(huì)有人說(shuō),用泛型啊,泛型不就是解決這個(gè)問(wèn)題的嗎?泛型必須指定一個(gè)特定的類(lèi)型。正式因?yàn)榉盒徒鉀Q不了...才提出了泛型的通配符。
4.3. 無(wú)界通配符無(wú)界通配符就是???吹竭@你可能會(huì)問(wèn),這不是跟T一樣嗎?為啥還要搞個(gè)?。他們主要區(qū)別在于,T主要用于聲明一個(gè)泛型類(lèi)或者方法,?主要用于使用泛型類(lèi)和泛型方法。下面舉個(gè)簡(jiǎn)單的例子。
// 定義打印任何類(lèi)型列表的函數(shù) public static void printList(List> list) { for (Object elem: list) { System.out.print(elem + " "); } } // 調(diào)用上述函數(shù) ListintList = Arrays.asList(1, 2, 3); List stringList = Arrays.asList("one", "two", "three"); printList(li);// 1 2 3 printList(ls);// one two three
上述函數(shù)的目的是打印任何類(lèi)型的列表??梢钥吹皆诤瘮?shù)內(nèi)部,并沒(méi)有關(guān)心List中的泛型到底是什么類(lèi)型的,你可以將>理解為只提供了一個(gè)只讀的功能,它去除了增加具體元素的能力,只保留與具體類(lèi)型無(wú)關(guān)的功能。從上述的例子可以看出,它只關(guān)心元素的數(shù)量以及其是否為空,除此之外不關(guān)心任何事。
再反觀T,上面我們也列舉了如何定義泛型的方法以及如果調(diào)用泛型方法。泛型方法內(nèi)部是要去關(guān)心具體類(lèi)型的,而不僅僅是數(shù)量和不為空這么簡(jiǎn)單。
4.4. 上界通配符 extends T>既然?可以代表任何類(lèi)型,那么extends又是干嘛的呢?
假設(shè)有這樣一個(gè)需求,我們只允許某一些特定的類(lèi)型可以調(diào)用我們的函數(shù)(例如,所有的Animal類(lèi)以及其派生類(lèi)),但是目前使用?,所有的類(lèi)型都可以調(diào)用函數(shù),無(wú)法滿(mǎn)足我們的需求。
private int countLength(List< ? extends Animal> list) {...}
使用了上界通配符來(lái)完成這個(gè)公共函數(shù)之后,就可以使用如下的方式來(lái)調(diào)用它了。
Listpigs = new ArrayList<>(); List dogs = new ArrayList<>(); List cats = new ArrayList<>(); // 假裝寫(xiě)入了數(shù)據(jù) int sum = 0; sum += countLength(pigs); sum += countLength(dogs); sum += countLength(cats);
看完了例子,我們就可以簡(jiǎn)單的得出一個(gè)結(jié)論。上界通配符就是一個(gè)可以處理任何特定類(lèi)型以及是該特定類(lèi)型的派生類(lèi)的通配符。
可能會(huì)有人看的有點(diǎn)懵逼,我結(jié)合上面的例子,再簡(jiǎn)單的用人話(huà)解釋一下:上界通配符就是一個(gè)啥動(dòng)物都能放的盒子。
4.5. 下界通配符 super Animal>上面我們聊了上界通配符,它將未知的類(lèi)型限制為特定類(lèi)型或者該特定的類(lèi)型的子類(lèi)型(也就是上面討論過(guò)的動(dòng)物以及一切動(dòng)物的子類(lèi))。而下界通配符則將未知的類(lèi)型限制為特定類(lèi)型或者該特定的類(lèi)型的超類(lèi)型,也就是超類(lèi)或者基類(lèi)。
在上述的上界通配符中,我們舉了一個(gè)例子。寫(xiě)了一個(gè)可以處理任何動(dòng)物類(lèi)以及是動(dòng)物類(lèi)的派生類(lèi)的函數(shù)。而現(xiàn)在我們要寫(xiě)一個(gè)函數(shù),用來(lái)處理任何是Integer以及是Integer的超類(lèi)的函數(shù)。
public static void addNumbers(List super Integer> list) { for (int i = 1; i <= 10; i++) { list.add(i); } }5. 類(lèi)型擦除
簡(jiǎn)單的了解了泛型的幾種簡(jiǎn)單的使用方法之后,我們回到本篇博客的主題上來(lái)——類(lèi)型擦除。泛型雖然有上述所列出的一些好處,但是泛型的生命周期只限于編譯階段。
本文最開(kāi)始的給出的樣例就是一個(gè)典型的例子。在經(jīng)過(guò)編譯之后會(huì)采取去泛型化的措施,編譯的過(guò)程中,在檢測(cè)了泛型的結(jié)果之后會(huì)將泛型的相關(guān)信息進(jìn)行擦除操作。就像文章最開(kāi)始提到的例子一樣,我們使用上面定義好的Generic泛型類(lèi)來(lái)舉個(gè)簡(jiǎn)單的例子。
Genericgeneric = new Generic<>("Hello"); Field[] fs = generic.getClass().getDeclaredFields(); for (Field f : fs) { System.out.println("type: " + f.getType().getName()); // type: java.lang.Object }
getDeclaredFields是反射中的方法,可以獲取當(dāng)前類(lèi)已經(jīng)聲明的各種字段,包括public,protected以及private。
可以看到我們傳入的泛型String已經(jīng)被擦除了,取而代之的是Object。那之前的String和Integer的泛型信息去哪兒了呢?可能這個(gè)時(shí)候你會(huì)靈光一閃,那是不是所有的泛型在被擦除之后都會(huì)變成Object呢?別著急,繼續(xù)往下看。
當(dāng)我們?cè)诜盒蜕厦媸褂昧松辖缤ㄅ浞院?,?huì)有什么情況發(fā)生呢?我們將Generic類(lèi)改成如下形式。
public class Generic{ T data; public Generic(T data) { setData(data); } public T getData() { return data; } public void setData(T data) { this.data = data; } }
然后再次使用反射來(lái)查看泛型擦除之后類(lèi)型。這次控制臺(tái)會(huì)輸出type: java.lang.String??梢钥吹?,如果我們給泛型類(lèi)制定了上限,泛型擦除之后就會(huì)被替換成類(lèi)型的上限。而如果沒(méi)有指定,就會(huì)統(tǒng)一的被替換成Object。相應(yīng)的,泛型類(lèi)中定義的方法的類(lèi)型也是如此。
6. 寫(xiě)在最后如果各位發(fā)現(xiàn)文章中有問(wèn)題的,歡迎大家不吝賜教,我會(huì)及時(shí)的更正。
參考:
Java語(yǔ)言類(lèi)型擦除
下界通配符
List>和List
的區(qū)別
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/74660.html
摘要:類(lèi)庫(kù)中提供了一套相當(dāng)完整的容器類(lèi)來(lái)解決這個(gè)問(wèn)題,其中基本類(lèi)型有,,,,這些對(duì)象類(lèi)型被稱(chēng)為集合類(lèi)。但是,類(lèi)庫(kù)中使用了來(lái)指代集合類(lèi)中的子集,,,所以集合類(lèi)也被稱(chēng)為容器。五類(lèi)型是能夠?qū)?duì)象映射到其他對(duì)象的一種容器,有區(qū)別于的方法。 引言 如果一個(gè)程序只包含固定數(shù)量的且其生命周期都是已知對(duì)象,那么這是一個(gè)非常簡(jiǎn)單的程序——《think in java》 了解容器前,先提出一個(gè)問(wèn)題,ArrayL...
類(lèi)型擦除 泛型被引入到Java語(yǔ)言中,以便在編譯時(shí)提供更嚴(yán)格的類(lèi)型檢查并支持通用編程,為了實(shí)現(xiàn)泛型,Java編譯器將類(lèi)型擦除應(yīng)用于: 如果類(lèi)型參數(shù)是無(wú)界的,則用它們的邊界或Object替換泛型類(lèi)型中的所有類(lèi)型參數(shù),因此,生成的字節(jié)碼僅包含普通的類(lèi)、接口和方法。 如有必要,插入類(lèi)型轉(zhuǎn)換以保持類(lèi)型安全。 生成橋接方法以保留擴(kuò)展泛型類(lèi)型中的多態(tài)性。 類(lèi)型擦除確保不為參數(shù)化類(lèi)型創(chuàng)建新類(lèi),因此,泛型不會(huì)...
摘要:然而中的泛型使用了類(lèi)型擦除,所以只是偽泛型??偨Y(jié)本文介紹了泛型的使用,以及類(lèi)型擦除相關(guān)的問(wèn)題。一般情況下泛型的使用比較簡(jiǎn)單,但是某些情況下,尤其是自己編寫(xiě)使用泛型的類(lèi)或者方法時(shí)要注意類(lèi)型擦除的問(wèn)題。 簡(jiǎn)介 Java 在 1.5 引入了泛型機(jī)制,泛型本質(zhì)是參數(shù)化類(lèi)型,也就是說(shuō)變量的類(lèi)型是一個(gè)參數(shù),在使用時(shí)再指定為具體類(lèi)型。泛型可以用于類(lèi)、接口、方法,通過(guò)使用泛型可以使代碼更簡(jiǎn)單、安全。然...
博客地址:Java泛型:類(lèi)型擦除 前情回顧 Java泛型:泛型類(lèi)、泛型接口和泛型方法 類(lèi)型擦除 代碼片段一 Class c1 = new ArrayList().getClass(); Class c2 = new ArrayList().getClass(); System.out.println(c1 == c2); /* Output true */ 顯然在平時(shí)使用中,ArrayList...
摘要:總結(jié)泛型的類(lèi)型必須是引用類(lèi)型,不能是基本類(lèi)型,泛型的個(gè)數(shù)可以有多個(gè),可以使用對(duì)創(chuàng)建對(duì)象時(shí)的泛型類(lèi)型以及方法參數(shù)類(lèi)型進(jìn)行限制,如使用關(guān)鍵字和對(duì)泛型的具體類(lèi)型進(jìn)行向下限制或向上限制,最后一點(diǎn),可以聲明泛型數(shù)組,但是不能創(chuàng)建泛型數(shù)組的實(shí)例。 自從 JDK 1.5 提供了泛型概念,泛型使得開(kāi)發(fā)者可以定義較為安全的類(lèi)型,不至于強(qiáng)制類(lèi)型轉(zhuǎn)化時(shí)出現(xiàn)類(lèi)型轉(zhuǎn)化異常,在沒(méi)有反省之前,可以通過(guò) Object...
閱讀 1181·2023-04-25 22:27
閱讀 945·2021-11-22 14:56
閱讀 1076·2021-11-11 16:54
閱讀 1804·2019-08-30 15:54
閱讀 3582·2019-08-30 13:20
閱讀 1274·2019-08-30 10:55
閱讀 2147·2019-08-26 13:34
閱讀 3344·2019-08-26 11:53