Effects of Type Erasure and Bridge Methods

有时类型擦除可能会导致我们无法预料的情况。下面的例子将介绍是如何产生的。例子Bridge Methods中展示了编译器有时会创建一个称为Bridge method(桥接函数)的合成方法,这是类型擦除过程的一部分。

下面代码中有两个类:

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);
    }
}

再看下面这段代码:

MyNode mn = new MyNode(5);
Node n = mn;            // A raw type - compiler throws an unchecked warning
n.setData("Hello");     // Causes a ClassCastException to be thrown.
Integer x = mn.data;

特殊说明:在Orcale的文档中报错的注释写在了最后一行Integer x = (String)mn.data; // Causes a ClassCastException to be thrown.。然而根据实际测试情况n.setData("Hello");就已经报错,运行JDK版本为jdk1.8.0_202

在经过类型擦除以后,上面的代码变成:

MyNode mn = new MyNode(5);
Node n = (MyNode)mn;         // A raw type - compiler throws an unchecked warning
n.setData("Hello");             // Causes a ClassCastException to be thrown.
Integer x = (String)mn.data;

当代码运行时发现了下面这些事:

  • n.setData("Hello");因为实际执行的是MyNode的实例中的setData(Object)函数。(MyNode类从Node继承了setData(Object)

  • setData(Object)函数体中,n所引用对象中的字段data被赋值为一个String

  • mn所引用的相关对象中的字段data,应该是能被访问的Integer(因为mnMyNode,也就是Node <Integer>)。

  • 尝试将一个String赋值给一个Integer会导致Java编译器在分配时插入强制转换,从而导致ClassCastException

Bridge Methods

当编译一个扩展参数化类或者实现参数化接口的类或者接口时,作为类型擦除过程的一部分,编译器可能需要创建一个被称为bridge method(桥接函数)的合成函数。

通常我们不需要担心桥接函数,但是如果其中一个出现在堆栈跟踪中,可能会让人感到困惑。

在类型擦除以后,NodeMyNode类型变成:

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);
    }
}

经过类型擦除,函数签名不在匹配。Node中的函数变为setData(Object)MyNode中的函数变为setData(Integer)。也就是说函数MyNode.setData比没有重写函数Node.setData

为了解决该问题并确保类型擦除以后泛型类型的[多态],Java编译器生成一个桥接函数来确保子类型能像预期的一样工作。以MyNode为例,编译器为setData生成如下的桥接函数:

class MyNode extends Node {

    // Bridge method generated by the compiler
    //
    public void setData(Object data) {
        setData((Integer) data);
    }

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

如上所见,在类型擦除以后桥接函数和Node类中的setData函数有着相同的函数签名,最总委托给原始的setData函数。

Last updated