javac是否会生成静态桥接方法?

Java中使用Bridge方法来处理派生方法中的协方差,并更改派生方法的可见性。

然而,这两种情况都是例如方法(因为你不能派生静态方法)。

我正在研究Kotlin如何生成参数默认值,我感到震惊,它使用静态桥接方法。

我想不出Javac产生静态桥接方法的情况 – 其他人可以吗? (通过这个,我的意思是一个ACC_BRIDGE标志(0x40)被设置的方法,而不仅仅是一个语义上的桥接方法)

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

方差

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

反编译

 public class BridgeTest1Derived extends BridgeTest1Base { @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()); } } 

根据Java语言规范的桥接方法(应该用ACC_BRIDGE进行注释的方法)可以确保覆盖兼容的签名 ,因此使用原始签名调用方法的代码将以重写的方法结束,即使它具有字节码级的不同方法签名。 Java编程语言中唯一的应用程序是types擦除和协变返回types。

由于static方法不能被调用者重定向的方式覆盖,所以在static方法中没有Java语言规范意义上的桥接方法。 因此, javac从不会生成一个ACC_BRIDGEACC_STATIC设置的方法。

代表另一种语言的语义将方法标记为桥接方法也是一个非常可疑的行为。 正如JVM规范所说:

ACC_BRIDGE标志用于指示编译器为Java编程语言生成的桥接方法。

还有其他的合成委托方法可能是static ,比如嵌套的类访问器或方法引用的适配器(例如可变参数或交集types)。 这些不算作桥梁方法。

AFAIK javac不会生成静态的桥接方法,但这并不意味着将来桥接方法将不再是静态的(我甚至不知道甚至是一个假设的例子)。

在两种情况下使用了Bridge方法:当你正在处理generics时,你已经显示了它,并且在覆盖时协同返回types。

在第一种情况下,当你实现一个通用接口或者扩展一个通用类的时候,会创建网桥方法。

对于接口,当你覆盖generics方法(必须是非静态的)时,将会创建一个桥接方法,但由于它将委托给非静态方法 ,所以它本身是非静态的。 那么,java-8允许在接口中使用静态方法,但是,它们是静态的,它们是不可重写的(它们甚至不是inheritance的,而不是类的静态方法)。

对于generics类,同样的故事就是这样。 只有实例方法是可重写的,即使创建了这样一个桥接方法,因为它会调用一个非静态的方法,它本身就是这样的。 小警告是类的静态方法是inheritance的,但它们是不可重写的(与接口的不同),因此没有桥接方法。

最后一个例子是协变返回types:

 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中将创建一个桥接方法,但是由于只能覆盖实例方法(我已经说了多少次了),所以桥接方法本身并不需要是静态的。

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

以这个来源为例:

 package bridge; public class C { private void doIt() { } class D { { doIt(); } } } 

Javap向您显示生成的静态access$0方法:

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

这是内容:

  static void access$0(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