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

时间:2016-01-26 15:46:31

标签: java generics type-erasure

我正在审查一个名为“Effects of Type Erasure and Bridge Methods”的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踪迹中这个明显的错误吗?

2 个答案:

答案 0 :(得分:3)

您误读了Oracle页面。如果你一直读到最后,你会发现它说明你所描述的是什么。

这不是一个写得很好的页面;作者说&#34; blah发生&#34;当他们的意思是&#34;如果是这样的情况那么发生,但我们认为这不是案例&#34;。他们的语言太松散了。

页面 - 桥梁方法 - 的重点是解释当你预测的行为(基于泛型设计+实现)是他们所建议的#34;时,你所观察到的真实行为是怎样的。一开始。

答案 1 :(得分:2)

嗯,这是在路上解释的。

理论中,当编译类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方法。一个是setData(Object),它继承自Node,另一个是setData(Integer)

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

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

然而,正如小道所说,编译器添加了一个“桥接”方法,使子类的行为更像您直观地思考它的方式。它向setData(Object)添加了MyNode覆盖,因此您无法调用原始的,不安全的Node.setData(Object)。在此覆盖桥方法中,有一个显式强制转换为Integer,可确保您无法为data分配非整数引用。

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

如果您在javap -p文件上运行MyNode.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);
}