为什么Java编译器为无参数的方法(getter方法)创建桥接方法

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) 所以看来重写已经完成了。为什么需要桥接方法?我缺少什么?

HTN*_*TNW 5

实际上,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 看来必须完全匹配。您不能放松参数类型或加强返回类型。

JVM 规范 SE 20,第 5.4.5 节

一个实例方法可以重写另一个实例方法,前提是满足以下所有条件:mCmA

  • mC具有与 相同的名称和描述符。mA
  • ...

这很重要,因为这是 JVM 在进行虚拟查找时决定调用哪个方法的方式。对对象的虚拟调用 ( invokevirtual)永远不会调用,因为在 JVM 看来,方法不会覆盖方法(即使在 Java 中也是如此)。这样的虚拟方法调用将调用,如果存在的话,否则在超类中查找这样的方法。Java 编译器为了实现返回类型协变,必须以 JVM 理解的唯一方式重写:通过定义.MySupplier#supply()ObjectHelloSupplierHelloSupplier#supply()Stringsupply()Stringsupply()ObjectHelloSupplier#supply()ObjectMySupplier#supply()ObjectHelloSupplier#supply()Object

因为 JVM 无论如何都不实现返回类型协变,所以 Java 编译器有时无法利用返回类型协变来避免为泛型代码发出桥接。