1. 程式人生 > >java遺珠之泛型型別擦除

java遺珠之泛型型別擦除

擦除規則

泛型的作用之前已經介紹過了只是用於編譯之前更為嚴格的型別檢查,其他的一些特性也都是編譯之前的,在編譯之後泛型是會被擦除掉的。

型別擦除所做的事情如下:

  1. 如果是無界限的則會把型別引數替換成Object,如果是有界限的則會把型別引數替換為界限型別。
  2. 插入一些型別轉換來保證型別安全
  3. 為了保證從泛型型別繼承的多型性會增加一些橋接方法。

泛型型別的擦除

無邊界型別擦除

public class Node<T> {

    private T data;
    private Node<T> next;

    public Node(T data, Node<
T>
next) { this.data = data; this.next = next; } public T getData() { return data; } // ... }

按照規則第一條T是無界的,會用Object來代替

public class Node {

    private Object data;
    private Node next;

    public Node(Object data, Node next) {
        this.data = data;
        this
.next = next; } public Object getData() { return data; } // ... }

有邊界型別擦除

public class Node<T extends Comparable<T>> {

    private T data;
    private Node<T> next;

    public Node(T data, Node<T> next) {
        this.data = data;
        this.next = next;
    }
public T getData() { return data; } // ... }

有界限的會替換成邊界型別

public class Node {

    private Comparable data;
    private Node next;

    public Node(Comparable data, Node next) {
        this.data = data;
        this.next = next;
    }

    public Comparable getData() { return data; }
    // ...
}

泛型方法的型別擦除

無邊界型別擦除

public static <T> int count(T[] anArray, T elem) {
    int cnt = 0;
    for (T e : anArray)
        if (e.equals(elem))
            ++cnt;
        return cnt;
}

和泛型型別同理,擦除如下:

public static <T> int count(T[] anArray, T elem) {
    int cnt = 0;
    for (T e : anArray)
        if (e.equals(elem))
            ++cnt;
        return cnt;
}

有邊界型別擦除

class Shape { /* ... */ }
class Circle extends Shape { /* ... */ }
class Rectangle extends Shape { /* ... */ }
public class Util {
    public static <T extends Shape> void draw(T shape) { /* ... */ }
}

型別擦除橋接方法

想想下面那個類的擦除

public class ANode<T> {

    public T data;

    public ANode(T data) { this.data = data; }

    public void setData(T data) {
        System.out.println("Node.setData");
        this.data = data;
    }

    public T getData() { return data; }
    // ...
}

會被擦除成

public class ANode {

    private Object data;

    public ANode(Object data) { this.data = data; }

    public void setData(Object data) {
        System.out.println("Node.setData");
        this.data = data;
    }

    public Object getData() { return data; }
    // ...
}

然後再看它的子類

public class MyNode extends ANode<Integer> {
    public MyNode(Integer data) {
        super(data);
    }
    
    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }
}

擦除之後

public class MyNode extends ANode {
    public MyNode(Integer data) {
        super(data);
    }
    
    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }
}

這就有意思了我們在main方法裡呼叫如下:

    public static void main(String[] args) {
        ANode n = new MyNode(5);
        n.setData("Hello");
        Integer x = (Integer) n.getData();    // Causes a ClassCastException to be thrown.
    }

如果只看擦除前的程式碼,setData("Hello")呼叫的應該是子類的方法,引數不應該是String才對。

但是程式碼肯定是經過擦除的,從這個角度來說

  1. setData("Hello")呼叫的引數其實Object,是父類的方法,因為子類的引數是Integer。
  2. 然後getData()返回的是Object型別,但指向的還是String型別,強轉依然會報錯。

嗯,到目前為止只是我們期望的,那麼一執行,蒙了,直接在setData("Hello")就報錯了。

Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
	at com.sweetop.studycore.generic.MyNode.main(MyNode.java:18)

這是因為為了保證泛型型別擦除後依然具有多型性,編譯器在編譯的時候自動增加了一個橋接方法,因此子類擦除後應該是這樣的

public class MyNode extends ANode {
    public MyNode(Integer data) {
        super(data);
    }
    
    public void setData(Object data) {
        setData((Integer) data);
    }
    
    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }
}

如果是這樣的話,那麼子類就重寫了父類的方法,呼叫的其實還是子類的setData(Object data).

因此就看到了上面那個錯誤。

之後碰到繼承泛型型別的時候,一定要注意這種問題。最好在使用之前先做下型別判斷

 public static void main(String[] args) {
        ANode n = new MyNode(5);
        Object a = "1";
        if (a instanceof Integer) {
            n.setData("1");
        }
        Integer x = (Integer) n.getData();    // Causes a ClassCastException to be thrown.
}