javac 会生成静态桥接方法吗?

Does javac ever generate static bridge methods?

桥接方法在 java 中用于处理派生方法中的协方差,并更改派生方法的可见性。

但是,这两种情况都是针对 实例 方法(因为您无法派生静态方法)。

我正在研究 Kotlin 如何生成参数默认值,令我震惊的是它使用了 static 桥接方法。

我想不出在什么情况下 Javac 会生成 static 桥接方法 - 其他人可以吗?(通过这个,我表示设置了 ACC_BRIDGE 标志 (0x40) 的方法,而不仅仅是语义桥接方法)

(fwiw - 示例代码和反编译(使用 cfr 0_124 和 --hidebridgemethods false))

方差

public class BridgeTest1Base<T> {
 public T frob() {
    return null;
 }
}

public class BridgeTest1Derived extends BridgeTest1Base<Integer> {
 public Integer frob() {
    return null;
 }
}

反编译为

public class BridgeTest1Derived extends BridgeTest1Base<Integer> {
 @Override
 public Integer frob() {
    return null;
 }

 @Override
 public /* bridge */ /* synthetic */ Object frob() {
    return this.frob();
 }
}

可见度

class BridgeTest2Base {
    public void frob() {
    }
}

public class BridgeTest2Derived extends BridgeTest2Base {}

反编译为

public class BridgeTest2Derived extends BridgeTest2Base {
 @Override
 public /* bridge */ /* synthetic */ void frob() {
    super.frob();
 }
}

Kotlin 默认值 - yum!

class frob2() {

fun fred2(x: Int = 300, y: frob2 = mkFrob2(x)) {
    println("{this}{x}{y}")
}

fun mkFrob2(x: Int): frob2 {
    return this;
}

fun foobar() {
    fred2();
    fred2(100);
    fred2(100, frob2());
}
}

反编译(到 java)到(注意静态桥)

public final class frob2 {
public final void fred2(int x, @NotNull frob2 y) {
    Intrinsics.checkParameterIsNotNull((Object)y, (String)"y");
    String string = "{this}{x}{y}";
    System.out.println((Object)string);
}

public static /* bridge */ /* synthetic */ void fred2$default(frob2 frob22, int n, frob2 frob23, int n2, Object object) {
    if ((n2 & 1) != 0) {
        n = 300;
    }
    if ((n2 & 2) != 0) {
        frob23 = frob22.mkFrob2(n);
    }
    frob22.fred2(n, frob23);
}

@NotNull
public final frob2 mkFrob2(int x) {
    return this;
}

public final void foobar() {
    frob2.fred2$default(this, 0, null, 3, null);
    frob2.fred2$default(this, 100, null, 2, null);
    this.fred2(100, new frob2());
}
}

是的,javac 生成静态桥接方法以允许内部 classes 访问私有外部 class 方法。

以这个来源为例:

package bridge;

public class C {
    private void doIt() {
    }

    class D {
        {
            doIt();
        }
    }
}

Javap 向您展示生成的静态 access[=13=] 方法:

$ javap -p -cp target/classes bridge.C
Compiled from "C.java"
public class bridge.C {
  public bridge.C();
  private void doIt();
  static void access[=11=](bridge.C);
}

内容:

  static void access[=12=](bridge.C);
    descriptor: (Lbridge/C;)V
    flags: ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #17                 // Method doIt:()V
         4: return

AFAIK javac 目前不会生成 static 桥接方法,这并不意味着将来桥接方法不会是静态的(虽然我什至不知道一个假设的例子)。

桥接方法在两种情况下使用:当您处理泛型时,如您所展示的那样,以及在重写时处理协变 return 类型。

在第一种情况下,桥接方法是在您实现泛型接口或扩展泛型时创建的 class。

对于接口,当您覆盖泛型方法(必须是非静态的)时,将创建一个桥接方法,但由于它将委托给 非静态 一,它本身是非静态的。好吧,java-8,允许接口内部的静态方法,但是,作为静态的,它们是不可覆盖的(它们甚至不是继承的,与来自 classes 的静态方法相反)。

对于通用 classes,情况相同。只有 instance 方法是可覆盖的,即使创建了这样的桥接方法,由于它会调用非静态方法,它本身也是这样。小警告是来自 classes 的静态方法是继承的,但它们不可覆盖(与接口方法相反),因此没有桥接方法。

最后一个例子是协变的 return 类型:

static class Parent {

}

static class Child extends Parent {

}

static class First {
    public Parent go() {
        return new Parent();
    }
}

static class SubFirst extends First {
    @Override
    public Child go() {
        return new Child();
    }
}

这里会在SubFirst中创建一个桥接方法,但是由于你只能覆盖实例方法(我已经说过多少次了?),所以桥接方法不需要是它自己静态。

根据 Java 语言规范的桥接方法,这些方法应该用 ACC_BRIDGE 注释,以确保 覆盖兼容签名 ,因此即使在字节码级别具有不同的方法签名,使用原始签名调用方法的代码也将以重写的方法结束。 Java 编程语言中的唯一应用是类型擦除和协变 return 类型。

由于 static 方法不能以调用者可能被重定向的方式被覆盖,因此没有任何场景可以出现 Java 语言规范意义上的桥接方法static 方法。因此,javac 永远不会生成设置了 ACC_BRIDGEACC_STATIC 的方法。

代表另一种语言的语义将方法标记为桥接方法也是一种非常值得怀疑的行为。正如 the JVM specification 所说:

The ACC_BRIDGE flag is used to indicate a bridge method generated by a compiler for the Java programming language.

还有其他合成委托方法可能是 static,例如嵌套 class 方法引用的访问器或适配器(例如 varargs 或交集类型).这些不算作桥接方法。