在接口中的静态方法上调用静态

invokestatic on static method in interface

反汇编一些 Java 8 代码 我发现一些 invokestatic 调用接口中的静态方法(特别是 java.util.function.Function.identity())使用常量池中的 InterfaceMethodRef;这就是 javap -s -c -v p 给我看的:

    15: invokestatic  #66  // InterfaceMethod java/util/function/Function.identity:()Ljava/util/function/Function;

根据 JVM 8 spec 这是不可能的,当我在版本为 Java 7 (major version=51) 的类文件中使用这条指令时,它在这条指令上抛出了 VerifyError .

然而,当我将主要版本更改为 52 时,它开始像一个魅力一样工作。请注意,我在 Oracle JDK 1.8.0_60 上 运行。我想知道为什么需要进行此更改(调用的方法是静态链接的,不是吗?)以及是否在任何地方对此进行了记录。

好吧,在 Java 8 之前,interface 中的 static 方法是不允许的,所以很明显,任何试图在以前的版本或 [=86] 中使用它们的尝试=] 具有旧版本的文件注定会失败,无论它在 Java 8.

中是如何实现的

在 Java 8 之前,我们有以下两个规则:

  1. The class_index item of a CONSTANT_Methodref_info structure must be a class type, not an interface type.

    The class_index item of a CONSTANT_InterfaceMethodref_info structure must be an interface type.

    (参见JVMSpec 7 §4.4.2

  2. invokestatic 的方法描述符必须引用 CONSTANT_Methodref_info 条目

    (参见JVMSpec 7 §6.5

    The run-time constant pool item at that index must be a symbolic reference to a method (§5.1), which gives the name and descriptor (§4.3.3) of the method as well as a symbolic reference to the class in which the method is to be found.

    “对方法的符号引用”排除接口方法可能看起来不太清楚,但如果没有这个假设,我们对 Java 8 的行为没有任何影响。通过与 Java 8 的 JVM 规范进行比较,或者考虑到接口方法总是暗示为非 static.

  3. ,它也会变得更加清晰

显然,要在 interface 中添加对 static 方法的支持,以便通过 invokestatic 调用,至少必须更改一条规则。

如果我们查看规则及其措辞,我们会发现第一个非常清楚,而在第二个中,“对方法的符号引用”指的是 CONSTANT_Methodref_info 并不完全明显条目并排除“对接口方法的符号引用”。决定是更改该规则并同时使措辞更清晰:

(JVMSpec 8 §6.5):

The run-time constant pool item at that index must be a symbolic reference to a method or an interface method (§5.1), which gives the name and descriptor (§4.3.3) of the method as well as a symbolic reference to the class or interface in which the method is to be found.

现在很明显,invokestatic可能是指接口方法,因此第一条规则不需要动,it hasn’t been touched。但请注意,第一条规则从未强制要求接口方法是非 static。只是关于声明类型是否为interface


这显然降低了 CONSTANT_Methodref_infoCONSTANT_InterfaceMethodref_info 之间的区别的价值,但这是不可避免的。如果第一条规则放宽,它也会软化这种区别。

但是有充分的理由改为更改调用方:由于引入了 default 方法,现在可以通过 [=28= 调用覆盖的 default 方法] 就像之前其他重写的非 abstract 方法一样。因此,invokespecial 现在也可以引用 interface 方法。

坚持调用指令的类型和常量池条目类型之间的匹配,就像旧规则一样,这意味着在 default 方法的情况下,我们有时需要两个池条目描述相同的目标方法,一个用于 invokeinterface,另一个用于 invokespecial.