美文网首页
泛型:类型擦除

泛型:类型擦除

作者: 安静的蓝孩子 | 来源:发表于2019-05-24 10:56 被阅读0次

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

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

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

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

泛型类擦除

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

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

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

编译器会把 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 <T> 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 <T extends Shape> void draw(T shape) { /* ... */ }

编译后会变成:

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

擦除和桥接函数

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

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

public class Node<T> {

    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<Integer> {
    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

相关文章:

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

相关文章

  • 【进阶之路】Java的类型擦除式泛型

    【进阶之路】Java的类型擦除式泛型 Java选择的泛型类型叫做类型擦除式泛型。什么是类型擦除式泛型呢?就是Jav...

  • Java如何在运行时获取泛型的类型

    Java泛型是伪泛型,会在编译完成时进行类型的擦除,我们无法在运行时获取泛型参数的具体类型(类型擦除会被替换成泛型...

  • JAVA泛型和类型擦除

    什么是类型擦除 Java是使用擦除来实现泛型的。使用泛型后在运行时任何具体的类型信息都被擦除了,关于泛型的处理都是...

  • [转载]泛型的内部原理:类型擦除以及类型擦除带来的问题

    泛型的内部原理:类型擦除以及类型擦除带来的问题

  • 泛型

    泛型用于编译时期,确保类型的安全 在运行时,会将泛型去掉,class文件是不带泛型的,这个称为泛型的擦除,擦除是为...

  • java泛型

    java的泛型是"伪泛型",为什么这么说。因为泛型只是作用在编译之前,编译之后,泛型都被擦除了(类型擦除)。所以说...

  • Android 开发也要掌握的Java知识 - Java泛型

    如果需要看泛型擦除Java泛型擦除 1.Java泛型有什么用?为啥要使用泛型? Java中数组的类型是定义的时候就...

  • Java 泛型之类型擦除和通配符PECS原则

    类型擦除 泛型是Java 5才引入的特性,在这之前,并没有泛型,所以Java的泛型和C++的不一样,是通过类型擦除...

  • 2018-04-28

    163课自定义泛型_深入1_子类_属性类型_重写方法类型_泛型擦除

  • Java 泛型知识点

    泛型 泛型函数 类型擦除 泛型用途 简单实例 传入长度,返回指定类型的数组 http://www.importne...

网友评论

      本文标题:泛型:类型擦除

      本文链接:https://www.haomeiwen.com/subject/rftgzqtx.html