Oracle';关于Java泛型的研究

Oracle';关于Java泛型的研究,java,generics,type-erasure,Java,Generics,Type Erasure,我当时正在查看Oracle关于Java泛型的一条线索,标题为“”,我无法说服自己给出了一个解释。出于好奇,我在本地测试了代码,甚至无法重现trail解释的行为。以下是相关代码: public class Node<T> { public T data; public Node(T data) { this.data = data; } public void setData(T data) { System.out.println("Nod

我当时正在查看Oracle关于Java泛型的一条线索,标题为“”,我无法说服自己给出了一个解释。出于好奇,我在本地测试了代码,甚至无法重现trail解释的行为。以下是相关代码:

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 = (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将不允许将
字符串
用于
setData()
。这对我来说是很直观的,它与我对泛型的理解是一致的。由于
MyNode
mn
是使用
Integer
构造的,因此编译器应该使用
Integer
强制执行对
setData()
的每次调用,以确保类型安全(即,正在传入一个
Integer


有人能解释一下Oracle跟踪中这个明显的错误吗?

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

这一页写得不好;作者说“胡说八道”,他们的意思是“如果是这样的话,那么胡说八道就会发生,但我们会看到事实并非如此”。他们的语言太松散了


页面桥接方法的要点是解释当预测行为(基于泛型acutal设计+实现)是他们在开始时“建议”的时候,你观察到的真实行为是怎样的。

好的,这在试验中解释

理论上,当类
节点
被编译时,它的基本类型
T
被擦除为
对象

所以在现实中,它被编译成

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(整数数据)
。就Java而言,这是
setData
方法的重载,而不是重写。每个
MyNode
对象都有两个
setData
方法。一个是
setData(Object)
它继承自
节点
,另一个是
setData(Integer)

因此,基本上,如果您使用raw类型,并使用任何非整数的引用调用
setData
,Java对此的正常解释是调用重载
setData(Object)

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

然而,正如trail所说,编译器添加了一个“桥”方法,使子类的行为更像您直观地想象的方式。它将
setData(Object)
覆盖添加到
MyNode
,因此您无法调用原始的非安全
节点。setData(Object)
。在这个覆盖桥方法中,有一个显式转换为
整数
,确保您不能将非整数引用分配给
数据

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

如果在
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);
}
类MyNode扩展节点{
公共MyNode(java.lang.Integer);
公共void setData(java.lang.Integer);
公共void setData(java.lang.Object);
}

我认为,说该页面的语言过于松散,给了它太多的信任。因此,您要说的是,调用
n.setData(“Hello”)
实际上是命中编译器生成的桥接方法,它将在调用超级方法之前将传递给它的每个对象转换为
Integer
。这就解释了我所想的一切,谢谢你的见解。我倾向于批评糟糕的文档,但我已经习惯于发布糟糕的文档,尤其是API文档。具有讽刺意味的是,Java在年轻时拥有一些最好的文档。现在。。。我尽量克制自己的轻蔑:)。
class MyNode extends Node<java.lang.Integer> {
  public MyNode(java.lang.Integer);
  public void setData(java.lang.Integer);
  public void setData(java.lang.Object);
}