将 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 the
User`:
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
的文档没有说明接口方法应该使用不同的查找算法,这意味着调用 interface
的 static
方法一般情况下是不可能的。
这显然不是规范的意图,它在 interface
中包含了 static
方法的显式添加功能,当然也应该是可调用的。
在您的示例中,调用 class 确实违反了规范,即 §4.4.2,因为它通过 CONSTANT_Methodref_info
而不是 CONSTANT_InterfaceMethodref_info
引用接口方法,在声明 class 被更改之后。但是 invokestatic
指令文档的当前状态并未强制要求根据常量池项的类型更改行为(这可能是实际意图,但并非如此)。而且,如前所述,坚持当前的措辞将意味着拒绝 interface
.
上的任何 invokestatic
因此规范需要再次清理,但由于 Methodref_info
和 InterfaceMethodref_info
之间的区别目前还没有 Java 8 之前那么有用(与上面链接的答案),如果最终的解决办法是在未来完全消除这种区别,我不会感到惊讶。
我遇到了以下 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 the
User`:
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
的文档没有说明接口方法应该使用不同的查找算法,这意味着调用 interface
的 static
方法一般情况下是不可能的。
这显然不是规范的意图,它在 interface
中包含了 static
方法的显式添加功能,当然也应该是可调用的。
在您的示例中,调用 class 确实违反了规范,即 §4.4.2,因为它通过 CONSTANT_Methodref_info
而不是 CONSTANT_InterfaceMethodref_info
引用接口方法,在声明 class 被更改之后。但是 invokestatic
指令文档的当前状态并未强制要求根据常量池项的类型更改行为(这可能是实际意图,但并非如此)。而且,如前所述,坚持当前的措辞将意味着拒绝 interface
.
invokestatic
因此规范需要再次清理,但由于 Methodref_info
和 InterfaceMethodref_info
之间的区别目前还没有 Java 8 之前那么有用(与上面链接的答案),如果最终的解决办法是在未来完全消除这种区别,我不会感到惊讶。