【发布时间】:2013-09-06 10:24:13
【问题描述】:
在阅读协变覆盖时,我发现了一个非常奇怪的事实,
协变方法覆盖是使用桥接技术实现的。 它还说这个特性是在java5及以上实现的。(我认为是因为从java5引入的泛型)
它是怎么发生的。请帮我举个例子。
【问题讨论】:
标签: java generics overriding covariant
在阅读协变覆盖时,我发现了一个非常奇怪的事实,
协变方法覆盖是使用桥接技术实现的。 它还说这个特性是在java5及以上实现的。(我认为是因为从java5引入的泛型)
它是怎么发生的。请帮我举个例子。
【问题讨论】:
标签: java generics overriding covariant
考虑一个例子:
public interface Shape<T extends Shape<T>> {
T getType();
void setType(T type);
}
public class Circle implements Shape<Circle> {
Circle getType() { }
void setType(Circle circle) { }
}
目前看起来还不错。但是,在类型擦除之后,接口失去了它的泛型类型,并且类型T 被替换为上限。所以接口和类看起来像:
public interface Shape {
Shape getType();
void setType(Shape type);
}
public class Circle implements Shape {
Circle getType() { }
void setType(Circle circle) { }
}
现在,问题来了。擦除后Circle 中的方法实际上并不是Shape 的重写版本。请注意,现在看起来的方法对其采用的参数和返回的值施加了更大的限制。这是因为擦除改变了接口中方法的签名。
为了解决这个问题,编译器为它们添加了桥接方法,它将调用委托给类中的那些实际方法。
所以,这个类真的被转换成:
public class Circle implements Shape {
Circle getType() { }
void setType(Circle circle) { }
// Bridge method added by compiler.
Shape getType() { return getType(); } // delegate to actual method
void setType(Shape shape) { setType((Circle)shape); } // delegate to actual method
}
因此,桥接方法现在是接口中方法的重写版本,它们将调用委托给执行任务的实际方法。
注意桥接方法中使用的类型是接口类型参数的擦除,本例为Shape。
参考资料:
【讨论】:
给定以下两个类:
public class Node<T> {
private 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);
}
}
在编译扩展参数化类或实现参数化接口的类或接口时,编译器可能需要创建一个称为桥接方法的合成方法,作为类型擦除过程的一部分。
为了解决这个问题并在类型擦除后保留泛型类型的多态性,Java 编译器会生成一个桥接方法来确保子类型化按预期工作。对于 MyNode 类,编译器为 setData 生成以下桥接方法:
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);
}
// ...
}
【讨论】:
这意味着如果您有一个具有协变(更窄)返回类型的方法,编译器将为您创建一个合成桥接方法,并通过此桥接方法调用覆盖方法。返回类型的协变是在 Java 5 中与泛型同时引入的,它是使用桥接方法实现的。
示例 1: 即使不存在泛型,也可以使用合成桥方法实现返回类型的协变。
例如:
abstract class A {
public abstract A get();
}
class B extends A {
@Override
public B get() {
return this;
}
}
将由编译器使用委托给原始方法的合成桥方法以字节码实现。原则上,您可以想象编译器将协变覆盖转换为:
abstract class A {
public abstract A get();
}
class B extends A {
//bytecode only bridge method
@Override
public A get() {
return get;
}
public B get() {
return this;
}
}
示例 2 - 泛型:让我们看一个涉及泛型的示例。
abstract class A<T> {
public abstract T get();
}
class B extends A<String> {
@Override
public String get() {
return "hello";
}
}
类B 的方法get() 在返回类型上与类A 中的方法get 是协变的。在编译这段代码时,编译器将执行 erasure,这意味着它将用它们的边界替换泛型并添加强制转换以确保一切正常。
擦除后的类如下所示:
abstract class A {
public abstract Object get();
}
class B extends A {
@Override
public String get() {
return "hello";
}
}
现在,由于get 的方法签名是public Object get(),这不是B 类中存在的签名,编译器将在B 类中生成一个桥接方法以实现覆盖。
您可以将B 类视为如下所示。 但是,请务必注意以下代码永远不会生成。它不会编译。编译器只是在 bytecode 中生成等效的 get 方法。
class B extends A {
//bridge method
@Override
public Object get() {
return get();
}
public String get() {
return "hello";
}
}
调用get 的B 到A 类的每个多态使用都会调用桥接方法,该桥接方法将委托给
真正的get方法。
【讨论】: