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