使用静态方法将类更改为Java中的接口的二进制兼容性

Сер*_*еев 5 java jvm language-specifications binary-compatibility

我遇到了以下奇怪的Java/JVM规范不完整的情况.假设我们有类(我们将使用Java 1.8和HotSpot):

public class Class {
  public static void foo() {
    System.out.println("hi");
  }
}

public class User {
  public static void main(String[] args) {
    Class.foo();
  }
}
Run Code Online (Sandbox Code Playgroud)

然后重新编译Class to be an interface without recompiling theUser`:

public interface Class {
  public static void foo() {
    System.out.println("hi");
  }
}
Run Code Online (Sandbox Code Playgroud)

运行User.mainnow会产生相同的输出hi.这似乎是显而易见的,但我希望它会失败,IncompatibleClassChangeError这就是为什么:

我知道根据JVM 5.3.5#3语句将类更改为接口是二进制不兼容:

如果命名为C的直接超类的类或接口实际上是一个接口,则加载会抛出一个IncompatibleClassChangeError.

但是我们假设我们没有继承者Class.我们现在必须参考JVM规范来了解方法的分辨率.第一个版本被编译成这个字节码:

public static void main(java.lang.String[]);
    Code:
       0: invokestatic  #2                  // Method examples/Class.foo:()V
       3: return
Run Code Online (Sandbox Code Playgroud)

所以我们这里有一个叫做CONSTANT_Methodref_infoclasspool的东西.

让我们引用invokestatic的动作.

...该索引处的运行时常量池项必须是对方法接口方法(第5.1节)的符号引用,它提供方法的名称和描述符(第4.3.3节)以及符号引用要在其中找到方法的类或接口.已解决指定的方法(第5.4.3.3节).

因此JVM以不同的方式处理方法和接口方法.在我们的例子中,JVM将该方法视为类的方法(而不是接口).JVM尝试相应地解决它5.4.3.3方法解析:

根据JVM规范,JVM必须在以下语句中失败:

1)如果C是接口,则方法解析会抛出IncompatibleClassChangeError.

...因为Class实际上不是一个类,而是一个接口.

不幸的是,我没有找到任何关于在Java语言规范第13章中将类更改为接口的二进制兼容性的提及.二进制兼容性.此外,没有任何关于引用相同静态方法的棘手案例的说法.

任何人都可以详细说明并告诉我,如果我错过了什么?

Hol*_*ger 4

首先,由于您的示例不包含继承,因此我们不需要 xe2x80x99t 假设我们没有 Classxe2x80x9d 的继承者,只需没有。因此 \xc2\xa75.3.5 的引用部分与此示例无关。

\n\n

\xc2\xa76.5 的引用部分,命名 \xe2\x80\x9c对方法或接口方法\xe2\x80\x9d 的符号引用,具有讽刺意味的是,Java\xc2\xa08 进行了更改以放宽限制。该invokestatic指令明确允许在接口方法上调用,如果它们是static指令。

\n\n

您在最后引用的第一个项目符号\xc2\xa75.4.3.3interface指出,如果声明类型是 an ,方法解析应该无条件失败,确实违反了,但无论如何它都没有意义。由于 it\xe2\x80\x99s 无条件引用,即invokestaticdoes\xe2\x80\x99t 的文档声明接口方法应使用不同的查找算法,这意味着static调用interface通常是不可能的。

\n\n

\xe2\x80\x99s 显然不是规范的意图,它包含了static方法中显式添加的功能interface的特性,当然这些方法也应该是可调用的。

\n\n

在您的示例中,调用类确实违反了规范,即\xc2\xa74.4.2 ,因为它在声明类更改后引用接口方法 viaCONSTANT_Methodref_info而不是。CONSTANT_InterfaceMethodref_info但是指令\xe2\x80\x99s文档的当前状态invokestatic并不\xe2\x80\x99s强制要求根据常量池项\xe2\x80\x99s类型更改行为(这可能是实际意图,但它\ xe2\x80\x99s 不存在)。而且,如前所述,遵守当前的措辞将意味着拒绝任何invokestatic关于interface.

\n\n

因此,规范需要另一次清理,但由于Methodref_info和之间的区别InterfaceMethodref_info到目前为止不如 Java\xc2\xa08 之前那么有用(与上面链接的答案相比),我不会\xe2\x80\x99 感到惊讶,如果最终的解决办法是在未来完全消除这种区别。

\n