将 class 与静态方法更改为接口的二进制兼容性 Java

Binary compatibility of changing a class with static methods to interface in Java

我遇到了以下 Java/JVM 规范不完整的奇怪案例。假设我们有 classes(我们将使用 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();
  }
}

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

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

运行 User.main 现在产生相同的输出 hi。这看起来很明显,但我希望它会因 IncompatibleClassChangeError 而失败,这就是为什么:

我知道根据 JVM 5.3.5#3 statement:

将 class 更改为接口是二进制不兼容

If the class or interface named as the direct superclass of C is, in fact, an interface, loading throws an IncompatibleClassChangeError.

但假设我们没有 Class 的继承者。我们现在必须参考 JVM 规范中有关方法解析的内容。 第一个版本被编译成这个字节码:

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

所以我们在 class 池中有一个叫做 CONSTANT_Methodref_info 的东西。

让我们引用invokestatic的动作。

... 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. The named method is resolved (§5.4.3.3).

因此 JVM 以不同的方式对待方法和接口方法。在我们的例子中,JVM 将方法视为 class 的方法(不是接口)。 JVM 尝试相应地解析它 5.4.3.3 方法解析:

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

1) If C is an interface, method resolution throws an IncompatibleClassChangeError.

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

不幸的是,我没有在 Java 语言规范第 13 章二进制兼容性中找到任何关于将 class 更改为接口的二进制兼容性的提及。此外,对于引用同一个静态方法这种棘手的情况也没有提及。

任何人都可以详细说明并告诉我是否遗漏了什么吗?

首先,由于您的示例不包含继承,我们不必“假设我们没有 Class 的继承者”,只需 none。因此,§5.3.5 的引用部分与此示例无关。

§6.5 的引用部分,命名为“对方法或接口方法的符号引用”具有讽刺意味的是 invokestatic 指令明确允许在接口方法上调用,如果它们是 static.

你最后提到的 §5.4.3.3 的第一个项目符号,声明方法解析应该无条件失败,如果声明类型是 interface,确实违反了,但它无论如何没有意义。由于它是无条件引用的,即 invokestatic 的文档没有说明接口方法应该使用不同的查找算法,这意味着调用 interfacestatic 方法一般情况下是不可能的。

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

在您的示例中,调用 class 确实违反了规范,即 §4.4.2,因为它通过 CONSTANT_Methodref_info 而不是 CONSTANT_InterfaceMethodref_info 引用接口方法,在声明 class 被更改之后。但是 invokestatic 指令文档的当前状态并未强制要求根据常量池项的类型更改行为(这可能是实际意图,但并非如此)。而且,如前所述,坚持当前的措辞将意味着拒绝 interface.

上的任何 invokestatic

因此规范需要再次清理,但由于 Methodref_infoInterfaceMethodref_info 之间的区别目前还没有 Java 8 之前那么有用(与上面链接的答案),如果最终的解决办法是在未来完全消除这种区别,我不会感到惊讶。