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