sta*_*s94 3 java generics overriding bridge
我确实理解需要创建桥接方法,例如需要传递参数的 setter 方法,但是 getter 方法呢?为什么Java也产生桥接方法呢?
下面是 ChatGPT 生成的虚拟代码,以使事情更加具体:
class Box<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
class StringBox extends Box<String> {
public void setValue(String value) {
System.out.println("Setting a String value in StringBox");
super.setValue(value.toString());
}
public String getValue() {
System.out.println("Getting String value from StringBox");
return super.getValue();
}
}
Run Code Online (Sandbox Code Playgroud)
擦除后的 Box 类将具有以下方法:
public Object getValue();
在 StringBox 类中,我们定义了该方法:
public String getValue()
但这两个方法具有相同的签名,并且重写方法的返回类型使用 String 作为返回类型,String 是 Object 的子类class(Covariant Return Type) 所以看来重写已经完成了。为什么需要桥接方法?我缺少什么?
实际上,JVM 并没有实现返回类型协变。即使没有泛型,Java 也需要桥接方法来在 JVM 上实现返回类型协变。如果编译以下内容,
class MySupplier {
public Object supply() { return null; }
}
class HelloSupplier extends MySupplier {
@Override public String supply() { return "Hello, World!"; }
}
Run Code Online (Sandbox Code Playgroud)
你会发现HelloSupplier
包含如下字节码:
class HelloSupplier extends MySupplier {
public String supply() { return "Hello, World!"; }
public /*bridge*/ Object supply() { return this.supply()/*String*/; }
// in bytecode, all method calls include the signature of the method, including the return type (which is written after the method name and parameters)
// the bridge method supply()Object calls the method supply()String
// note that bytecode allows overloading on return type, even though Java doesn't
}
Run Code Online (Sandbox Code Playgroud)
如果您查看覆盖的JVM定义,您会注意到它与Java的覆盖概念不同,因为覆盖的签名在 JVM 看来必须完全匹配。您不能放松参数类型或加强返回类型。
一个实例方法可以重写另一个实例方法,前提是满足以下所有条件:
mC
mA
mC
具有与 相同的名称和描述符。mA
- ...
这很重要,因为这是 JVM 在进行虚拟查找时决定调用哪个方法的方式。对对象的虚拟调用 ( invokevirtual
)永远不会调用,因为在 JVM 看来,方法不会覆盖方法(即使在 Java 中也是如此)。这样的虚拟方法调用将调用,如果存在的话,否则在超类中查找这样的方法。Java 编译器为了实现返回类型协变,必须以 JVM 理解的唯一方式重写:通过定义.MySupplier#supply()Object
HelloSupplier
HelloSupplier#supply()String
supply()String
supply()Object
HelloSupplier#supply()Object
MySupplier#supply()Object
HelloSupplier#supply()Object
因为 JVM 无论如何都不实现返回类型协变,所以 Java 编译器有时无法利用返回类型协变来避免为泛型代码发出桥接。