一、概述
Java泛型在使用過程有許多的問題,如不存在List
二、編譯器如何處理泛型?
通常情況下,一個編譯器處理泛型有兩種方式:
1.Code specialization。在實例化一個泛型類別或泛型方法時都產生一份新的目標代碼(字節碼or二進位代碼)。例如,針對一個泛型list,可能需要 針對string,integer,float產生三個目標程式碼。
2.Code sharing。對每個泛型類別只產生唯一的一份目標代碼;該泛型類別的所有實例都映射到這份目標代碼上,在需要的時候執行類型檢查和類型轉換。
C++中的模板(template)是典型的Code specialization實作。 C++編譯器會為每一個泛型類別實例產生一份執行程式碼。執行程式碼中integer list和string list是兩種不同的型別。這樣會導致代碼膨脹(code bloat),不過有經驗的C++程式設計師可以有技巧的避免代碼膨脹。
Code specialization另一個弊端是在引用型別系統中,浪費空間,因為引用型別集合中元素本質上都是指標。沒必要為每個類型產生一份執行程式碼。而這也是Java編譯器中採用Code sharing方式處理泛型的主要原因。
Java編譯器透過Code sharing方式為每個泛型型別建立唯一的字節碼表示,並且將該泛型類型的實例都對應到這個唯一的字節碼表示上。將多種泛型類形實例對應到唯一的字節碼表示是透過類型擦除(type erasue)實現的。
三、 什麼是類型擦除?
類型擦除指的是透過型別參數合併,將泛型型別實例關聯到同一份字節碼上。編譯器只為泛型類型產生一份字節碼,並將其實例關聯到這份字節碼上。類型擦除的關鍵在於從泛型類型中清除類型參數的相關信息,並且再必要的時候添加類型檢查和類型轉換的方法。
類型擦除可以簡單的理解為將泛型java程式碼轉換為普通java程式碼,只不過編譯器更直接點,將泛型java程式碼直接轉換成普通java字節碼。
類型擦除的主要過程如下:
1.將所有的泛型參數以其最左邊界(最頂級的父類型)類型替換。
2.移除所有的型別參數。
如
interface Comparable <A> { public int compareTo( A that); } final class NumericValue implements Comparable <NumericValue> { priva te byte value; public NumericValue (byte value) { this.value = value; } public byte getValue() { return value; } public int compareTo( NumericValue t hat) { return this.value - that.value; } } ----------------- 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; } } final class Test { public static void main (String[ ] args) { LinkedList <NumericValue> numberList = new LinkedList <NumericValue> (); numberList .add(new NumericValue((byte)0)); numberList .add(new NumericValue((byte)1)); NumericValue y = Collections.max( numberList ); } }<span style="color: #333333; font-family: Arial; font-size: 14px;"> </span>
經過類型擦除後的類型為
interface Comparable {
public int compareTo( Object that); } final class NumericValue implements Comparable { priva te byte value; public NumericValue (byte value) { this.value = value; } public byte getValue() { return value; } public int compareTo( NumericValue t hat) { return this.value - that.value; } public int compareTo(Object that) { return this.compareTo((NumericValue)that); } } ------------- class 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; } } final class Test { public static void main (String[ ] args) { LinkedList numberList = new LinkedList(); numberList .add(new NumericValue((byte)0)); , numberList .add(new NumericValue((byte)1)); NumericValue y = (NumericValue) Collections.max( numberList ); } }<span style="color: #333333; font-family: Arial; font-size: 14px;"> </span>
第一個泛型類別Comparable 擦除後 A被替換為最左邊界邊界。 Comparable
第二個範例中限定了型別參數的邊界>A,A必須為Comparable的子類,依照型別擦除的過程,先講所有的型別參數 ti換為最左邊界Comparable,然後去掉參數類型A,得到最終的擦除後結果。
四、類型擦除所帶來的問題
正是由於類型擦除的隱藏存在,直接導致了眾多的泛型靈異問題。
Q1.用同一泛型類別的實例區分方法簽章? ——NO!
import java.util.*;
public class Erasure{ public void test(List<String> ls){ System.out.println("Sting"); } public void test(List<Integer> li){ System.out.println("Integer"); } }<span style="color: #333333; font-family: Arial; font-size: 14px;"> </span>
編譯此類,
参数类型明明不一样啊,一个List
Q2. 同时catch同一个泛型异常类的多个实例?——NO!
同理,如果定义了一个泛型一场类GenericException
Q3.泛型类的静态变量是共享的?——Yes!
猜猜这段代码的输出是什么?
import java.util.*; 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){} }<span style="color: #333333; font-family: Arial; font-size: 14px;"> </span>
答案是——2!由于经过类型擦除,所有的泛型类实例都关联到同一份字节码上,泛型类的所有静态变量是共享的。
五、Just remember
1.虚拟机中没有泛型,只有普通类和普通方法
2.所有泛型类的类型参数在编译时都会被擦除
3.创建泛型对象时请指明类型,让编译器尽早的做参数检查(Effective Java,第23条:请不要在新代码中使用原生态类型)
4.不要忽略编译器的警告信息,那意味着潜在的ClassCastException等着你。
更多Java泛型-类型擦除相关文章请关注PHP中文网!