类型消除和 bridge 方法的影响
有时类型擦除会导致您可能无法预料的情况。以下示例显示了这种情况的发生方式。该示例(在Bridge Methods中描述)显示了编译器有时如何创建综合方法(称为 bridge 方法)作为类型擦除过程的一部分。
给定以下两类:
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 = 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.
执行代码时会发生以下情况:
-
n\.setData\("Hello"\);
使方法setData\(Object\)
在类MyNode
的对象上执行。 (MyNode
类从Node
继承了setData\(Object\)
.) -
在
setData\(Object\)
的主体中,由n
引用的对象的数据字段分配给String
。 -
可以访问通过
mn
引用的同一对象的数据字段,并且该字段应为整数(因为mn
是MyNode
,而Node\<Integer\>
。 -
try将
String
分配给Integer
会导致 Java 编译器在分配时插入的强制转换产生ClassCastException
。
Bridge Methods
在编译扩展了参数化类的类或interface或实现参数化interface的类时,作为类型擦除过程的一部分,编译器可能需要创建一个称为* bridge method *的综合方法。您通常不必担心 bridge 方法,但是如果其中一个出现在堆栈跟踪中,您可能会感到困惑。
类型擦除后,Node
和MyNode
类变为:
public 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;
}
}
public class MyNode extends Node {
public MyNode(Integer data) { super(data); }
public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
}
类型擦除后,方法签名不匹配。 Node
方法变为setData\(Object\)
,而MyNode
方法变为setData\(Integer\)
。因此,MyNode
setData
方法不会覆盖Node
setData
方法。
为了解决此问题并在类型擦除后保留泛型的polymorphism,Java 编译器生成了一个 bridge 方法以确保子类型能够按预期工作。对于MyNode
类,编译器为setData
生成以下 bridge 方法:
class MyNode extends Node {
// Bridge method generated by the compiler
//
public void setData(Object data) {
setData((Integer) data);
}
public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
// ...
}
如您所见,bridge 方法(在类型擦除后与Node
类的setData
方法具有相同的方法签名)将委托给原始的setData
方法。