甲骨文在Java泛型方面的一个潜在问题

我正在审查Java泛型的Oracle着作之一,名为“类型擦除和桥接方法的影响”,我无法说服自己给出解释。 好奇的是,我在本地测试了这些代码,甚至无法重现线索解释的行为。 这里是相关的代码:

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页面。 如果你一直读到最后,你会发现它说明发生了什么是你描述的。

这不是一个写得很好的页面; 作者说,当他们的意思是“如果有这种情况发生了等等,但是我们认为那不是这种情况”,那么“等等发生了”。 他们的语言过于宽松。

页面 - 桥接方法 - 的要点是,当预测的行为(基于泛型实例设计+实现)是他们在开始时“建议”的时候,解释实际行为是如何观察的。


那么,在路上解释。

理论上讲,当类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;
    }
}

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

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

这不会导致赋值问题,因为data被声明为Object ,而不是Integer 。 只有当您尝试将数据分配回Integer引用时,问题才会发生。 这种简单的Java行为会导致MyNode对象被不适当的数据“污染”。

然而,正如线索所言,编译器添加了一个“桥接”方法,使得子类的行为更像您直觉地认为它的方式。 它向MyNode添加了setData(Object)重写 ,这样就不能调用原始的,非安全的Node.setData(Object) 。 在这种压倒一切的电桥法,有一个显式的Integer ,以确保您将无法分配非整数参考data

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

如果你在MyNode.class文件上运行javap -p ,你会发现它确实有两个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);
}
链接地址: http://www.djcxy.com/p/89863.html

上一篇: Potential issue with one of Oracle's trails on Java generics

下一篇: How do I get array item type in TypeScript using the Reflection API?