javac 11 link 方法应该如何在具有 Java 8 目标的更高版本中被覆盖?

How should javac 11 link methods overridden in later versions with a Java 8 target?

假设我使用的是 Java 11 javac,但我使用的 --source--target 选项设置为 1.8,这样我的源代码就会被考虑Java 8 并且输出 .class 文件将与 Java 8 兼容。我的目标是生成 .class 可以 运行 在 Java 8 JVM.

假设我正在编译以下 Java 8 代码:

import java.nio.ByteBuffer;

…

ByteBuffer byteBuffer = …; //init somehow
byteBuffer.flip(); //what ends up in the `.class` file?

问题是: Java 11 javac 应该在 .class 文件中放入什么来 link byteBuffer.flip() 方法调用?在你回答之前,考虑一下:

所以重述一下问题:应该 Java 11 javac,将 --source--target 选项设置为 1.8,生成一个 .class 文件 link 到 Buffer.flip()ByteBuffer.flip()?如果是前者,那么它怎么知道不包括 ByteBuffer.flip() 而不是,因为 (Java 8) 代码清楚地引用了 ByteBuffer.flip() 和 (Java 11) 编译器看到运行时间有ByteBuffer.flip()方法?但如果是后者,那么我怎么知道我 100% 正确的 Java 8 兼容源代码,当使用 Java 11 编译时,会 运行 在 Java 8 JRE 即使我使用 --source--target 选项来指示 Java 8? (请注意,OpenJDK 11.0.5 似乎选择了后者。但哪个是正确的?)

(请注意,我在松散地使用“link”这个词;我目前并不精通生成的字节码。我所知道的是 class 文件具有以某种方式引用 Buffer.flip()ByteBuffer.flip(); 如果在 运行 时找不到此方法,JVM 将抛出异常,例如: java.lang.NoSuchMethodError: java.nio.ByteBuffer.flip()Ljava/nio/ByteBuffer;.)

作为附加问题,我想知道使用为 Java 8 设置的 --release 选项是否会改变答案。但请注意,我不能使用 --release 选项(相当于 Maven <release> 编译器插件选项),因为我希望我的 Maven 项目可以使用 Java 8 和 [=81] 构建=] 11.

如果我们采用以下代码并使用 Java 8 和 Java 11 进行编译,我们将得到以下字节码,如 运行ning javap -c MyClass.class 时所见。

Java源代码

ByteBuffer byteBuffer = ByteBuffer.allocate(64);
byteBuffer.flip();

Java 8字节码

 0: bipush        64
 2: invokestatic  #19   // Method java/nio/ByteBuffer.allocate:(I)Ljava/nio/ByteBuffer;
 5: astore_1
 6: aload_1
 7: invokevirtual #25   // Method java/nio/ByteBuffer.flip:()Ljava/nio/Buffer;
10: pop

Java 11字节码

 0: bipush        64
 2: invokestatic  #19   // Method java/nio/ByteBuffer.allocate:(I)Ljava/nio/ByteBuffer;
 5: astore_1
 6: aload_1
 7: invokevirtual #25   // Method java/nio/ByteBuffer.flip:()Ljava/nio/ByteBuffer;
10: pop

如您所见,它们都“link”到 ByteBufferflip() 方法,即使没有为 Java 声明该方法8.

但是,在字节码级别,方法签名包括 return 类型。这意味着 JVM 支持您可以重载仅 return 类型不同的方法的语言,即使 Java 不支持。

Java11版本的方法有不同的return类型,可以在linked方法中看到,在()之后,其中 Java 8 显示 return 类型为 Ljava/nio/Buffer;,Java 11 显示 return 类型为 Ljava/nio/ByteBuffer;.

当您使用针对 Java 11 运行时库编译的代码,并在 Java 8 上尝试 运行 编译它时,您会得到 Exception in thread "main" java.lang.NoSuchMethodError: java.nio.ByteBuffer.flip()Ljava/nio/ByteBuffer;

这就是为什么你应该总是指定bootstrapclass路径指向一个Java 与目标 Java 版本匹配的运行时库。当您使用 Java 11 的 javac 和选项 -source 8 -target 8 进行编译时,它实际上会警告您:

warning: [options] bootstrap class path not set in conjunction with -source 8
1 warning

这就是他们实施更新的 --release <release> 选项来替换 -source-target 的原因。如果使用 --release 8 编译,生成的 .class 文件将 运行 在 Java 8.

上没有错误

更新

您无需安装 Java 8 即可使用选项 --release 8。 Java 11 安装知道 Java 8 运行时库的方法是什么。

--release 选项是在 Java 9 中实施的 JEP 247: Compile for Older Platform Versions 的结果,它说:

For JDK N and --release M, M < N, signature data of the documented APIs of release M of the platform is needed. This data is stored in the $JDK_ROOT/lib/ct.sym file, which is similar, but not the same, as the file of the same name in JDK 8. The ct.sym file is a ZIP file containing stripped-down class files corresponding to class files from the target platform versions.