Java的類型擦除

  • 時間:2019-02-24 21:06 作者:墨雨軒夏 來源:墨雨軒夏 閱讀:208
  • 掃一掃,手機訪問
摘要:一、各種語言中的編譯器是如何解決泛型的通常情況下,一個編譯器解決泛型有兩種方式:1.Code specialization。在實例化一個泛型類或者泛型方法時都產生一份新的目標代碼(字節碼or二進制代碼)。例如,針對一個泛型list,可能需要 針對string,integer,float產生三份目標代碼

一、各種語言中的編譯器是如何解決泛型的

通常情況下,一個編譯器解決泛型有兩種方式:

1.Code specialization。在實例化一個泛型類或者泛型方法時都產生一份新的目標代碼(字節碼or二進制代碼)。例如,針對一個泛型list,可能需要 針對string,integer,float產生三份目標代碼。

2.Code sharing。對每個泛型類只生成唯一的一份目標代碼;該泛型類的所有實例都映射到這份目標代碼上,在需要的時候執行類型檢查和類型轉換。

C++ 中的模板(template)是典型的Code specialization實現。 C++ 編譯器會為每一個泛型類實例生成一份執行代碼。執行代碼中integer list和string list是兩種不同的類型。這樣會導致 代碼膨脹(code bloat) 。 C# 里面泛型無論在程序源碼中、編譯后的IL中(Intermediate Language,中間語言,這時候泛型是一個占位符)或者是運行期的CLR中都是切實存在的,List<int>與List<String>就是兩個不同的類型,它們在系統運行期生成,有自己的虛方法表和類型數據,這種實現稱為類型膨脹,基于這種方法實現的泛型被稱為真實泛型。 Java語言中的泛型則不一樣,它只在程序源碼中存在,在編譯后的字節碼文件中,就已經被替換為原來的原生類型(Raw Type,也稱為裸類型)了,并且在相應的地方插入了強制轉型代碼,因而對于運行期的Java語言來說,ArrayList<int>與ArrayList<String>就是同一個類。所以說泛型技術實際上是Java語言的一顆語法糖,Java語言中的泛型實現方法稱為 類型擦除 ,基于這種方法實現的泛型被稱為偽泛型。

C++和C#是使用Code specialization的解決機制,前面提到,他有一個缺點,那就是 會導致代碼膨脹 。另外一個弊端是在引用類型系統中,白費空間,由于引用類型集合中元素本質上都是一個指針。沒必要為每個類型都產生一份執行代碼。而這也是Java編譯器中采用Code sharing方式解決泛型的主要起因。

Java編譯器通過Code sharing方式為每個泛型類型創立唯一的字節碼表示,并且將該泛型類型的實例都映射到這個唯一的字節碼表示上。將多種泛型類形實例映射到唯一的字節碼表示是通過 類型擦除 (type erasue)實現的。

二、什么是類型擦除

前面我們屢次提到這個詞: 類型擦除 (type erasue)**,那么究竟什么是類型擦除呢?

類型擦除指的是通過類型參數合并,將泛型類型實例關聯到同一份字節碼上。編譯器只為泛型類型生成一份字節碼,并將其實例關聯到這份字節碼上。類型擦除的關鍵在于從泛型類型中清理類型參數的相關信息,并且再必要的時候增加類型檢查和類型轉換的方法。 類型擦除可以簡單的了解為將泛型java代碼轉換為普通java代碼,只不過編譯器更直接點,將泛型java代碼直接轉換成普通java字節碼。 類型擦除的主要過程如下: 1.將所有的泛型參數用其最左邊界(最頂級的父類型)類型替換。(這部分內容可以看:Java泛型中extends和super的了解) 2.移除所有的類型參數。

三、Java編譯器解決泛型的過程

code 1:

public static void main(String[] args) {

Map<String, String> map = new HashMap<String, String>();

map.put("name", "hollis");

map.put("age", "22");

System.out.println(map.get("name"));

System.out.println(map.get("age"));

}

反編譯后的code 1:

public static void main(String[] args) {

Map map = new HashMap();

map.put("name", "hollis");

map.put("age", "22");

System.out.println((String) map.get("name"));

System.out.println((String) map.get("age"));

}

我們發現泛型都不見了,程序又變回了Java泛型出現之前的寫法,泛型類型都變回了原生類型,

code 2:

interface Comparable<A> {

public int compareTo(A that);

}

public final class NumericValue implements Comparable<NumericValue> {

private byte value;

public NumericValue(byte value) {

this.value = value;

}

public byte getValue() {

return value;

}

public int compareTo(NumericValue that) {

return this.value - that.value;

}

}

反編譯后的code 2:

interface Comparable {

public int compareTo( Object that);

}

public final class NumericValue

implements Comparable

{

public NumericValue(byte value)

{

this.value = value;

}

public byte getValue()

{

return value;

}

public int compareTo(NumericValue that)

{

return value - that.value;

}

public volatile int compareTo(Object obj)

{

return compareTo((NumericValue)obj);

}

private byte value;

}

code 3:

public class Collections {

public static <A extends Comparable<A>> A max(Collection<A> xs) {

Iterator<A> xi = xs.iterator();

A w = xi.next();

while (xi.hasNext()) {

A x = xi.next();

if (w.compareTo(x) < 0)

w = x;

}

return w;

}

}

反編譯后的code 3:

public class Collections

{

public Collections()

{

}

public static Comparable max(Collection xs)

{

Iterator xi = xs.iterator();

Comparable w = (Comparable)xi.next();

while(xi.hasNext())

{

Comparable x = (Comparable)xi.next();

if(w.compareTo(x) < 0)

w = x;

}

return w;

}

}

第2個泛型類Comparable <A>擦除后 A被替換為最左邊界Object。Comparable<NumericValue>的類型參數NumericValue被擦除掉,但是這直 接導致NumericValue沒有實現接口Comparable的compareTo(Object that)方法,于是編譯器充任好人,增加了一個 橋接方法 。 第3個示例中限定了類型參數的邊界<A extends Comparable<A>>A,A必需為Comparable<A>的子類,按照類型擦除的過程,先講所有的類型參數 ti換為最左邊界Comparable<A>,而后去掉參數類型A,得到最終的擦除后結果。

四、泛型帶來的問題

一、當泛型遇到重載:

public class GenericTypes {

public static void method(List<String> list) {

System.out.println("invoke method(List<String> list)");

}

public static void method(List<Integer> list) {

System.out.println("invoke method(List<Integer> list)");

}

}

上面這段代碼,有兩個重載的函數,由于他們的參數類型不同,一個是List<String>另一個是List<Integer> ,但是,這段代碼是編譯通不過的。由于我們前面講過,參數List<Integer>和List<String>編譯之后都被擦除了,變成了一樣的原生類型List ,擦除動作導致這兩個方法的特征簽名變得一模一樣。

二、當泛型遇到catch:

假如我們自己設置了一個泛型異常類GenericException ,那么,不要嘗試用多個catch取匹配不同的異常類型,例如你想要分別捕獲GenericException 、GenericException ,這也是有問題的。

三、當泛型內包含靜態變量

public class StaticTest{

public static void main(String[] args){

GT<Integer> gti = new GT<Integer>();

gti.var=1;

GT<String> gts = new GT<String>();

gts.var=2;

System.out.println(gti.var);

}

}

class GT<T>{

public static int var=0;

public void nothing(T x){}

}

答案是------2!因為經過類型擦除,所有的泛型類實例都關聯到同一份字節碼上,泛型類的所有靜態變量是共享的。

五、總結

虛擬機中沒有泛型,只有普通類和普通方法,所有泛型類的類型參數在編譯時都會被擦除,泛型類并沒有自己獨有的Class類對象。比方并不存在List<String>.class或者是List<Integer>.class,而只有List.class。

創立泛型對象時請指明類型,讓編譯器盡早的做參數檢查( Effective Java,第23條:請不要在新代碼中使用原生態類型 )

不要忽略編譯器的警告信息,那意味著潛在的ClassCastException等著你。

靜態變量是被泛型類的所有實例所共享的。對于公告為MyClass<T>的類,訪問其中的靜態變量的方法依然是 MyClass.myStaticVar。不論是通過new MyClass<String>還是new MyClass<Integer>創立的對象,都是共享一個靜態變量。

泛型的類型參數不能用在Java異常解決的catch語句中。由于異常解決是由JVM在運行時刻來進行的。因為類型信息被擦除,JVM是無法區分兩個異常類型MyException<String>和MyException<Integer>的。對于JVM來說,它們都是 MyException類型的。也就無法執行與異常對應的catch語句。

?為了讓學習變得輕松、高效,今天給大家免費分享一套Java教學資源。幫助大家在成為Java架構師的道路上披荊斬棘。需要資料的歡迎加入學習交流群:9285,05736

  • 全部評論(0)
最新發布的資訊信息
【系統環境|服務器應用】在CentOS 7上如何安裝Gogs 0.11.53(2020-02-10 10:14)
【系統環境|】淘碼庫,據消息稱已被調查。淘碼庫源碼網,已經無法訪問!(2020-01-14 04:13)
【系統環境|服務器應用】Discuz隱藏后臺admin.php網址修改路徑(2019-12-16 16:48)
【系統環境|服務器應用】2020新網站如何讓百度快速收錄網站首頁最新方法,親測有用!免費(2019-12-16 16:46)
【系統環境|服務器應用】Discuz發布帖子時默認顯示第一個主題分類的修改方法(2019-12-09 00:13)
【系統環境|軟件環境】Android | App內存優化 之 內存泄漏 要點概述 以及 處理實戰(2019-12-04 14:27)
【系統環境|軟件環境】MySQL InnoDB 事務(2019-12-04 14:26)
【系統環境|軟件環境】vue-router(單頁面應用控制中心)常見用法(2019-12-04 14:26)
【系統環境|軟件環境】Linux中的Kill命令(2019-12-04 14:26)
【系統環境|軟件環境】Linux 入門時必學60個文件解決命令(2019-12-04 14:26)
手機二維碼手機訪問領取大禮包
返回頂部
3d开机号查询