泛型:类型擦除

Java 语言引入泛型是为了在编译时提供更严格的类型检查,并支持泛型编程。

为了实现泛型,Java编译器将类型擦除应用于:

  • 用边界值替换泛型类型中的所有类型参数,如果是无限边界的,则使用 Object 替换。因此,生成的字节码只包含普通类、接口和方法。
  • 如果需要,强制类型转换,以确保类型安全。
  • 生成桥接方法来保存扩展泛型类型中的多态性。

类型擦除确保不会为参数化类型创建新类。因此,泛型不会产生运行时开销。

泛型类擦除

在类型擦除过程中,Java 编译器擦除所有类型参数,如果类型参数有界,则用它的第一个边界替换每个参数,如果没有边界则用 Object 替换。

比如下边这个单向链表节点的泛型类,

public class Node {

    private T data;
    private Node next;

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

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

因为类型参数 T 是无边界的,编译器会用 Object 替换 T

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; }
    // ...

如果我们把 Node 改成有边界的泛型类,如下:

public class Node> {

    private T data;
    private Node next;

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

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

编译器会把 T 替换成第一个边界值 Comparable,如:

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; }
    // ...
}

泛型方法擦除

Java 编译器也会擦除泛型方法参数中的类型参数。

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

因为 T 是无边界的,所以编译器会用 Object 代替它。

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

在比如:

class Shape { /* ... */ }
class Circle extends Shape { /* ... */ }
class Rectangle extends Shape { /* ... */ }

public static  void draw(T shape) { /* ... */ }

编译后会变成:

public static void draw(Shape shape) { /* ... */ }

擦除和桥接函数

有时候因为类型擦除会导致一些意想不到的情况。

下面的例会解释如何发生的。这个例子(在桥接方法中描述)向我们展示了编译器在类型擦除过程中,如何创建一个合成方法(称为桥接方法)。

public class Node {

    public T data;

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

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

public class MyNode extends Node {
    public MyNode(Integer data) { super(data); }

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

调用代码

public class Node {

    public Object data;

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

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

public class MyNode extends Node {

    public MyNode(Integer data) { super(data); }

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

MyNode mn = new MyNode(5);
Node n = mn;            
n.setData("Hello");     // 编译没有报错,运行时抛出异常
Integer x = mn.data;  

编译后类型擦除后的代码:

MyNode mn = new MyNode(5);
Node n = (MyNode)mn;         
n.setData("Hello");
Integer x = (String)mn.data;   

这个异常是怎么造成的呢?
NodesetData 的参数类型为 Object 的,而 MyNode 需要的是 Integer,这里会报一个 ClassCastException 的异常。

具体的原因,是因为编译器在编译一个继承自泛型类的子类时,为了方法覆盖的签名匹配,保留泛型类型的多态性,会生成一个桥接方法。

class MyNode extends Node {

    // 编译器生成的桥接方法
    public void setData(Object data) {
        setData((Integer) data);
    }

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

    // ...
}

在擦除后,Node 的方法变成 setData(Object)MyNode 的方法变成了 setData(Integer),为了覆盖 Node 的方法,编译器在 MyNode 生成了一个 public void setData(Object data) 的桥接方法,这也是导致问题的原因.






Type Erasure


相关文章:

泛型:为什么使用泛型与泛型的基本使用
泛型:边界和通配符
泛型:类型擦除

你可能感兴趣的:(泛型:类型擦除)