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

在Java中将具有静态方法的类更改为接口的二进制兼容性,java,jvm,binary-compatibility,language-specifications,Java,Jvm,Binary Compatibility,Language Specifications,我遇到了以下Java/JVM规范不完整的奇怪情况。假设我们有这些类(我们将使用Java 1.8和HotSpot): 然后将类重新编译为接口,而不重新编译用户`: public interface Class { public static void foo() { System.out.println("hi"); } } 现在运行User.main会产生相同的输出hi。这似乎很明显,但我希望它会失败,出现不兼容ClassChangeError,这就是为什么: 我知道将类更改为

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

然后将
类重新编译为接口,而不重新编译
用户`:

public interface Class {
  public static void foo() {
    System.out.println("hi");
  }
}
现在运行
User.main
会产生相同的输出
hi
。这似乎很明显,但我希望它会失败,出现
不兼容ClassChangeError
,这就是为什么:

我知道将类更改为接口是二进制不兼容的,根据:

如果名为C的直接超类的类或接口实际上是一个接口,则加载会抛出一个
不兼容的ClassChangeError

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

public static void main(java.lang.String[]);
    Code:
       0: invokestatic  #2                  // Method examples/Class.foo:()V
       3: return
我们在类池中有一个叫做
CONSTANT\u Methodref\u info
的东西

让我们引述一个例子

。。。该索引处的运行时常量池项必须是对方法接口方法(§5.1)的符号引用,它给出了方法的名称和描述符(§4.3.3),以及对要在其中找到方法的类或接口的符号引用。命名方法已解析(§5.4.3.3)

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

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

1) 如果C是一个接口,方法解析会抛出一个不兼容的ClassChangeError

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

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


有谁能详细说明一下,告诉我我是否遗漏了什么吗?

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

具有讽刺意味的是,§6.5中引用的命名“方法或接口方法的符号参考”的部分是一个错误。如果接口方法是
静态的
,则明确允许调用
invokestatic
指令

您在最后提到的第一个项目符号指出,如果声明类型是
接口
,则方法解析应无条件失败,这确实违反了,但无论如何都没有意义。由于它是无条件引用的,即
invokestatic
的文档中没有说明接口方法应使用不同的查找算法,这意味着调用
接口
静态
方法通常是不可能的

这显然不是本规范的意图,它包含了
接口
s中显式添加的
静态
方法的特性,当然也应该是可调用的

在您的示例中,调用类确实违反了规范,也就是说,在声明类被更改后,它通过
常量方法ref\u info
而不是
常量方法ref\u info
引用接口方法。但是,
invokestatic
指令文档的当前状态并不要求根据常量池项的类型更改行为(这可能是实际意图,但并不存在)。如前所述,坚持当前的措辞意味着拒绝
接口上的任何
invokestatic

因此规范需要另一次清理,但由于
Methodref_info
InterfaceMethodref_info
之间的区别远没有Java8之前那么有用(与上面链接的答案相比),我不会感到惊讶,如果最终的解决方案是在将来完全消除这种区别。

顺便说一句,我发现:
API类接口的性别变化破坏了二进制兼容性,即使在类/接口由客户端使用但未由客户端实现的情况下也是如此。这是因为用于调用接口中声明的方法的Java VM字节码与用于调用类中声明的方法的Java VM字节码不同。但这是非正式的。
public static void main(java.lang.String[]);
    Code:
       0: invokestatic  #2                  // Method examples/Class.foo:()V
       3: return