日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java的类型擦除

發布時間:2024/7/23 java 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java的类型擦除 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

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

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

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

總結

以上是生活随笔為你收集整理的Java的类型擦除的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。