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 不允许 String
与 setData()
一起使用。这对我来说其实很直观,也符合我对泛型的理解。由于 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 -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);
}
我正在审查关于 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 不允许 String
与 setData()
一起使用。这对我来说其实很直观,也符合我对泛型的理解。由于 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 -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);
}