Java泛型:類型擦除

類型擦除

代碼片斷一

Class c1 = new ArrayList<Integer>().getClass();
Class c2 = new ArrayList<String>().getClass(); 
System.out.println(c1 == c2);

/* Output
true
*/

顯然在平時使用中,ArrayList<Integer>()new ArrayList<String>()是徹底不一樣的類型,可是在這裏,程序卻的的確確會輸出true編程

這就是Java泛型的類型擦除形成的,由於不論是ArrayList<Integer>()仍是new ArrayList<String>(),都在編譯器被編譯器擦除成了ArrayList。那編譯器爲何要作這件事?緣由也和大多數的Java讓人不爽的點同樣——兼容性。因爲泛型並非從Java誕生就存在的一個特性,而是等到SE5才被加入的,因此爲了兼容以前並未使用泛型的類庫和代碼,不得不讓編譯器擦除掉代碼中有關於泛型類型信息的部分,這樣最後生成出來的代碼實際上是『泛型無關』的,咱們使用別人的代碼或者類庫時也就不須要關心對方代碼是否已經『泛化』,反之亦然。設計模式

在編譯器層面作的這件事(擦除具體的類型信息),使得Java的泛型先天都存在一個讓人很是難受的缺點:數組

在泛型代碼內部,沒法得到任何有關泛型參數類型的信息。

代碼片斷二

List<Integer> list = new ArrayList<Integer>();
Map<Integer, String> map = new HashMap<Integer, String>();
System.out.println(Arrays.toString(list.getClass().getTypeParameters()));
System.out.println(Arrays.toString(map.getClass().getTypeParameters()));

/* Output
[E]
[K, V]
*/

關於getTypeParameters()的解釋:函數

Returns an array of TypeVariable objects that represent the type variables declared by the generic declaration represented by this GenericDeclaration object, in declaration order. Returns an array of length 0 if the underlying generic declaration declares no type variables.

咱們期待的是獲得泛型參數的類型,可是實際上咱們只獲得了一堆佔位符。this

代碼片斷三

public class Main<T> {

    public T[] makeArray() {
        // error: Type parameter 'T' cannot be instantiated directly
        return new T[5];
    }
}

咱們沒法在泛型內部建立一個T類型的數組,緣由也和以前同樣,T僅僅是個佔位符,並無真實的類型信息,實際上,除了new表達式以外,instanceof操做和轉型(會收到警告)在泛型內部都是沒法使用的,而形成這個的緣由就是以前講過的編譯器對類型信息進行了擦除。spa

同時,面對泛型內部形如T var;的代碼時,記得多念幾遍:它只是個Object,它只是個Object……設計

代碼片斷四

public class Main<T> {

    private T t;

    public void set(T t) {
        this.t = t;
    }

    public T get() {
        return t;
    }

    public static void main(String[] args) {
        Main<String> m = new Main<String>();
        m.set("findingsea");
        String s = m.get();
        System.out.println(s);
    }
}

/* Output
findingsea
*/

雖然有類型擦除的存在,使得編譯器在泛型內部其實徹底沒法知道有關T的任何信息,可是編譯器能夠保證重要的一點:內部一致性,也是咱們放進去的是什麼類型的對象,取出來仍是相同類型的對象,這一點讓Java的泛型起碼仍是有用武之地的。code

代碼片斷四展示就是編譯器確保了咱們放在t上的類型的確是T(即使它並不知道有關T的任何類型信息)。這種確保其實作了兩步工做:對象

  • set()處的類型檢驗blog

  • get()處的類型轉換

這兩步工做也成爲邊界動做。

代碼片斷五

public class Main<T> {

    public List<T> fillList(T t, int size) {
        List<T> list = new ArrayList<T>();
        for (int i = 0; i < size; i++) {
            list.add(t);
        }
        return list;
    }

    public static void main(String[] args) {
        Main<String> m = new Main<String>();
        List<String> list = m.fillList("findingsea", 5);
        System.out.println(list.toString());
    }
}

/* Output
[findingsea, findingsea, findingsea, findingsea, findingsea]
*/

代碼片斷五一樣展現的是泛型的內部一致性。

擦除的補償

如上看到的,但凡是涉及到確切類型信息的操做,在泛型內部都是沒法共工做的。那是否有辦法繞過這個問題來編程,答案就是顯示地傳遞類型標籤。

代碼片斷六

public class Main<T> {

    public T create(Class<T> type) {
        try {
            return type.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) {
        Main<String> m = new Main<String>();
        String s = m.create(String.class);
    }
}

代碼片斷六展現了一種用類型標籤生成新對象的方法,可是這個辦法很脆弱,由於這種辦法要求對應的類型必須有默認構造函數,遇到Integer類型的時候就失敗了,並且這個錯誤還不能在編譯器捕獲。

進階的方法能夠用限制類型的顯示工廠和模板方法設計模式來改進這個問題,具體能夠參見《Java編程思想 (第4版)》P382。

代碼片斷七

public class Main<T> {

    public T[] create(Class<T> type) {
        return (T[]) Array.newInstance(type, 10);
    }

    public static void main(String[] args) {
        Main<String> m = new Main<String>();
        String[] strings = m.create(String.class);
    }
}

代碼片斷七展現了對泛型數組的擦除補償,本質方法仍是經過顯示地傳遞類型標籤,經過Array.newInstance(type, size)來生成數組,同時也是最爲推薦的在泛型內部生成數組的方法。

以上,泛型的第二部分的結束。

相關文章
相關標籤/搜索