我遇到了以下 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 the
User`:
public interface Class {
public static void foo() {
System.out.println("hi");
}
}
运行User.main
now 会产生相同的输出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 章中找到关于将类更改为接口的二进制兼容性的任何提及。二进制兼容性。此外,对于引用相同静态方法的这种棘手情况,也没有任何说法。
如果我错过了什么,有人可以详细说明并告诉我吗?