Java 接口合成方法生成,同时缩小返回类型范围

Java interface synthetic method generation while narrowing return type

提问人:Roman Lebedev 提问时间:9/23/2019 最后编辑:Roman Lebedev 更新时间:9/25/2019 访问量:274

问:

我有 2 个接口和 2 个返回类型。

interface interfaceA {
   Publisher<String> doSomething();
}

interface interfaceB extends interfaceA {
   Flow<String> doSomething();
}

interface Publisher<T>{}
class Flow<T> implements Publisher<T>{}

所以在运行时,我可以看到 2 个方法interfaceB.class.getMethods()

  1. public default my.package.Publisher my.package.interfaceB.doSomething()

  2. public abstract my.package.Flow my.package.interfaceB.doSomething()

关于第一个,它是合成的。(method.getModifiers() & 0x00001000 > 0) == true

java 会自动生成这种合成方法吗?

它通常如何工作?

Java 继承 JVM

评论

1赞 Johannes Kuhn 9/23/2019
这些东西被称为桥接方法。它们是必需的,因为对于 JVM,方法类型由所有参数组成,并且是返回类型。
1赞 Pshemo 9/23/2019
可能相关:docs.oracle.com/javase/tutorial/java/generics/...
1赞 Johannes Kuhn 9/23/2019
要深入了解桥接方法,您可以观看 Dan Heidinga 和 Brian Goetz 的这段视频: youtube.com/watch?v=kOBHtmqavXc

答:

0赞 Ambro-r 9/24/2019 #1

Java 语言规范指定了以下内容:

如果 Java 编译器发出的构造与源代码中显式或隐式声明的构造不对应,则必须将其标记为合成构造,除非发出的构造是类初始化方法

我从 Java 论文中看到的另一个定义是:

当封闭类访问嵌套类的私有属性时,Java 编译器会为该属性创建合成方法。

编辑:有人指出,本文所述的一些内容适用于较旧的Java版本(SE7),因此在阅读在线论文时要注意。

例如:

import static java.lang.System.out;

public class Synthetic {

  public static void main(String[] arguments) {
      Synthetic.NestedClass nested = new Synthetic.NestedClass();
      out.println("Age: " + nested.age);
  }

  private static final class NestedClass {
      
      private String myName = "Tom";

      private int age = 42;

  }

}

编译器将为正在访问的每个私有属性创建合成方法。因此,它将为 创建合成方法 (),但不会static int access$000(Synthetic$NestedClass)agemyName

评论

0赞 Johannes Kuhn 9/24/2019
此示例不会使用 Java 11+ 创建任何合成方法。
0赞 Holger 9/24/2019
@JohannesKuhn默认构造函数不是合成的。所以从 JDK 11 开始,这个例子确实不会有合成成员。无论如何,这个答案是错误的,因为它说“将被标记为合成”,而只有生成的访问器方法才会被标记。链接的论文的陈述“如果源代码中有一个可用的 getter 方法,那么这个合成方法将不会被创建”也是错误的,因为编译器不会将字段访问转换为 getter 调用。NestedClass
0赞 Ambro-r 9/25/2019
@Johannes,关于你对 Java 11 的陈述,我运行了两个测试。首先,我用 Java 8 编译了代码,然后使用 Java 类文件反汇编器 () on 和 for 来反编译以创建 .如果我运行相同的步骤但使用 JDK 11,结果完全相同。所以从本质上讲,它正在创建一个合成的访问方法(甚至在 Java 11 中也是如此)。javapSynthetic$NestedClass.classNestedClass agestatic int access$000(Synthetic$NestedClass);age
0赞 Ambro-r 9/25/2019
@Holger,我可以看到关于吸气剂的陈述是多么混乱。正如我之前所看到的那样,对此进行了一些阅读,事实证明这是 SE7 中发生的事情。同样,关于标记为合成的东西。只会相应地更新上面的答案。发送 (TIL)
1赞 Holger 9/25/2019
目前尚不清楚为什么 JDK 11 中仍然使用合成方法。您是否使用了 or 选项,或者旧版本或 Eclipse 编译器的 IDE 设置?我刚刚验证了通过正确的设置,Java 11 中没有合成访问器方法。这也适用于 的附加合成构造函数。-target--releaseNestedClass
2赞 Johannes Kuhn 9/24/2019 #2

您在这里看到的称为桥接方法。

为了理解为什么需要这样做,我们必须看看 JVM 如何确定两种方法是否不同:

  • 每个方法都有一个名称。不同的名称 - >不同的方法。
  • 每个方法都有一个描述符。不同的描述符>不同的方法。

描述符包含所有参数和返回类型(对于泛型,它是擦除的)。

从 JVM 的角度来看,它与 是不同的方法,因此当要求它对 进行 invokeinterface 调用时,它不会调用 。Flow doSomething()Publisher doSomething()Publisher doSomething()Flow doSomething()

如果调用站点的目标类型为:interfaceA

intefaceA foo = ...;
foo.doSomething();

但从语言的角度来看,这两种方法是相同的,并且一种会覆盖另一种。

若要还原此关系,请添加一个具有原始方法类型的桥接方法,该方法类型仅调用重载方法。javac