5

我遇到了以下 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();
  }
}

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

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

运行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

所以我们在这里有一个叫做CONSTANT_Methodref_info类池的东西。

让我们引用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 章中找到关于将类更改为接口的二进制兼容性的任何提及。二进制兼容性。此外,对于引用相同静态方法的这种棘手情况,也没有任何说法。

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

4

1 回答 1

4

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

§6.5 中引用的部分,命名“<em>对方法或接口方法的符号引用”具有讽刺意味的是,Java 8 所做的更改是为了放松限制invokestatic明确允许在接口方法上调用该指令,如果它们是static.

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

这显然不是规范的意图,它结合了 s 中static方法的显式添加特性interface,当然也应该是可调用的。

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

Methodref_info所以规范需要再次清理,但由于和之间的区别InterfaceMethodref_info到目前为止不如 Java 8 之前有用(与上面链接的答案相比),如果最终修复结果是,我不会感到惊讶将来完全消除这种区别。

于 2017-02-17T15:02:17.590 回答