泛型擦除

擦除的現象

當開始深刻研究泛型的時,會發現其實有些東西是沒有意義的。例如,咱們能夠聲明ArrayList.class,可是卻沒法聲明ArrayList<Integer>.class
這是由於泛型的擦除機制形成的,考慮如下的狀況。數組

public class ErasedTypeEquivalence {
    public static void main(String[] args) {
        Class c1 = new ArrayList<String>().getClass();
        Class c2 = new ArrayList<Integer>().getClass();
        System.out.println("(c1 == c2) = " + (c1 == c2));
    }
}

以上代碼中,代表ArrayList<String>ArrayList<Integer>是同一類型。不一樣的類型在行爲方面確定不一樣。例如,若是試着將一個Integer類型放入ArrayList<String>,所得的行爲和Integer類型放入ArrayList<Integer>徹底不一樣,可是它們仍然是同一類型。
如下的代碼是對這個問題的一個補充。安全

class Frob {
}

class Fnorkle {
}

class Quark<Q> {
}

class Particle<POSITION, MOMENTUM> {
}

public class LostInfomation {
    public static void main(String[] args) {
        List<Frob> list = new ArrayList<>();
        Map<Frob,Fnorkle> map = new HashMap<>();
        Quark<Fnorkle> quark = new Quark<>();
        Particle<Long,Double> p = new Particle<>();
        System.out.println(Arrays.toString(
                list.getClass().getTypeParameters()
        ));
        System.out.println(Arrays.toString(
                map.getClass().getTypeParameters()
        ));
        System.out.println(Arrays.toString(
                quark.getClass().getTypeParameters()
        ));
        System.out.println(Arrays.toString(
                p.getClass().getTypeParameters()
        ));
    }
}
// Outputs
[E]
[K, V]
[Q]
[POSITION, MOMENTUM]

Class.getTypeParameters是返回一個TypeVariable對象數組,表示泛型聲明所聲明的類型參數。可是上例的輸出也代表了,這個方法得到的只是作參數佔位符的標識符。學習

擦除的概念

在泛型代碼內部,沒法得到任何有關泛型參數類型的信息。
Java的泛型是使用擦除來實現的,這就意味着在使用泛型的時候,任何具體的類型信息都會被擦除。寫代碼時惟一知道就是在使用一個對象。所以,List<String>List<Integer>在運行時事實上是相同的類型。這兩種形式都會被擦除成它們的"原生"類型,即List。理解擦除以及應該如何處理它,是在學習Java泛型時候的最大阻礙。ui

所以,能夠得到類型參數標識符和泛型類型邊界這樣的信息,可是卻沒法知道用來建立某個特定實例的實際的類型參數。this

擦除的邊界

class HasF{
    public void f(){
        System.out.println("HasF.f()");
    }
}

class Manipulator<T> {
    private T obj;

    public Manipulator(T obj) {
        this.obj = obj;
    }

    public void manipulate(){
        //obj.f() compile error
    }
}

public class Manipulation {
    public static void main(String[] args) {
        HasF hf = new HasF();
        Manipulator<HasF> manipulator = new Manipulator<>(hf);
        manipulator.manipulate();
    }
}

因爲有了擦除機制,Java編譯器沒法將manipulate()必須可以在obj上調用f()這一需求映射到HasF擁有f()這一事實上,爲了調用f(),咱們必須協助泛型類,給定泛型類的邊界,以便告知編譯器只能遵循這個邊界的類型。這裏重用了extends關鍵字。並因爲有了邊界,下面的代碼能夠編譯了。spa

class Manipulator2<T extends HasF> {
    private T obj;

    public Manipulator2(T obj) {
        this.obj = obj;
    }

    public void manipulate(){
        obj.f();
    }
}

上面的代碼中,邊界<T extentds HasF>聲明T必須具備類型HasF或者從HasF導出來的類型,由於這個約束,因此能夠安全地在obj上調用f了。
這裏說泛型的類型參數將擦除到它的第一邊界(泛型可能有多個邊界)。這裏提到了類型參數的擦除,編譯器實際上會把類型參數替換成它的擦除,就像上面的示例那樣,T擦除到了HasF,就像在類的聲明中用HasF替換成T同樣。
如同上文所說,咱們能夠不使用泛型,直接將T替換回會HasFcode

class Manipulator3 {
    private HasF obj;

    public Manipulator2(HasF obj) {
        this.obj = obj;
    }

    public void manipulate(){
        obj.f();
    }
}

上面的代碼也能夠像Manipulator2中那樣正常工做。可是這並不意味着帶邊界的泛型是毫無心義的。
只有當但願使用的類型參數比某個具體類型(以及它的全部子類型)更加"泛化"時。也就是說,當但願代碼能跨多個類工做的時候,使用泛型纔有幫助。所以,類型參數和它們在有用的泛型代碼中的應用,一般比簡單的類替換要更爲複雜。。可是也不能由於以爲<T extends HasF>的任何東西都是有缺陷的。
例如,假設某個類有返回T的方法,那麼泛型在這裏就是有用處的,由於泛型能夠返回確切的類型。例子以下。對象

class ReturnGenericType<T extends HasF> {
    private T obj;

    public ReturnGenericType(T obj) {
        this.obj = obj;
    }

    public T getObj() {
        return obj;
    }
}

因此,必須查看全部的代碼。並肯定它是否"足夠複雜"到必須使用泛型的程度。ip

相關文章
相關標籤/搜索