Oracle 关于 Java 泛型的线索之一的潜在问题

Potential issue with one of Oracle's trails on Java generics

我正在审查关于 Java 泛型的 Oracle 线索之一,标题为“Effects of Type Erasure and Bridge Methods”,但我无法说服自己接受给出的解释。好奇的是,我在本地测试了代码,但我什至无法重现踪迹所解释的行为。这是相关代码:

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

Oracle 线索声称此代码段具有以下行为:

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

此代码片段在类型擦除后应如下所示:

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

我不明白这里使用的转换或行为。当我使用 IntelliJ 和 Java 7 在本地尝试 运行 这段代码时,我得到了这个行为:

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;

换句话说,JVM 不允许 StringsetData() 一起使用。这对我来说其实很直观,也符合我对泛型的理解。由于 MyNode mn 是用 Integer 构造的,编译器应该将每次对 setData() 的调用强制转换为 Integer 以确保类型安全(即 Integer 正在传入)。

有人可以阐明 Oracle 线索中这个明显的错误吗?

您误读了 Oracle 页面。如果你一直读到最后,你会发现它说发生的事情就是你所描述的。

这不是 well-written 页面;作者说 "blah happens" 而他们的意思是 "IF THIS WERE THE CASE THEN blah happens BUT AS WE'LL SEE THAT IS NOT THE CASE"。他们的语言太松散了。

页面 - 桥接方法 - 的重点是解释实际行为如何,如您观察到的那样,当预测行为(基于 Generics 实际设计 + 实现)是他们开始时 "suggested" .

嗯,在trail中有解释。

理论中,当编译class Node时,其基类型T被擦除为Object

所以实际上,它被编译成类似

的东西
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;
    }
}

然后你创建一个 child class MyNode,它有自己的 setData(Integer data)。就 Java 而言,这是对 setData 方法的 重载 ,而不是对其的覆盖。每个 MyNode object 有两个 setData 方法。一个是setData(Object)继承自Node,另一个是setData(Integer).

所以基本上,如果您使用原始类型,并使用任何不是 Integer 的引用调用 setData,Java 对此的正常解释是调用重载 setData(Object).

这不会导致赋值问题,因为 data 被声明为 Object,而不是 Integer。只有当您尝试将数据分配回 Integer 引用时才会出现问题。 Java 的这种简单行为会导致 MyNode object 成为 "contaminated" 不适当的数据。

然而,正如 trail 所说,编译器添加了一个 "bridge" 方法来使 child class 的行为更像您直观地想到的方式。它将 setData(Object)override 添加到 MyNode,这样你就不能调用原来的 non-safe Node.setData(Object)。在这个重写的桥接方法中,有一个显式转换为 Integer 以确保您无法将 non-integer 引用分配给 data.

这就是您在实际编译和 运行 示例时看到的行为。

如果你 运行 javap -pMyNode.class 文件上,你会看到它确实有两个 setData 方法:

class MyNode extends Node<java.lang.Integer> {
  public MyNode(java.lang.Integer);
  public void setData(java.lang.Integer);
  public void setData(java.lang.Object);
}