假设我正在使用 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
文件链接 byteBuffer.flip()
方法调用?在回答之前,请考虑以下问题:ByteBuffer
也不是 Java 11 ByteBuffer
声明 flip()
方法在 ByteBuffer
等级。 ByteBuffer
是 Buffer
的直接子类.两者都有Java 8 Buffer.flip()
和一个 Java 11 Buffer.flip()
在 Buffer
中声明两个版本的 API。 ByteBuffer.flip()
方法。 ByteBuffer
覆盖 Buffer.flip()
方法如下。目的显然是使用协方差,以便在 Java 11 ByteBuffer.flip()
中会方便地返回 ByteBuffer
而不是 Buffer
.@Override
public ByteBuffer flip() {
super.flip();
return this;
}
所以重申这个问题:Java 11 是否应该
javac
, 与 --source
和 --target
选项设置为 1.8
,生成 .class
链接到 Buffer.flip()
的文件或到 ByteBuffer.flip()
?如果是前者,那怎么知道不包括ByteBuffer.flip()
相反,因为 (Java 8) 代码清楚地引用了 ByteBuffer.flip()
并且(Java 11)编译器看到有一个 ByteBuffer.flip()
运行时的方法?但如果是后者,那么我怎么知道我的 100% 正确的 Java 8 兼容源代码,当使用 Java 11 编译时,即使我使用 --source
也会在 Java 8 JRE 上运行和 --target
指示 Java 8 的选项? (请注意,OpenJDK 11.0.5 似乎选择了后者。但哪个是正确的?)(请注意,我松散地使用了“链接”这个词;我目前不熟悉生成什么字节码。我所知道的是类文件以某种方式引用了
Buffer.flip()
或 ByteBuffer.flip()
;和如果在运行时找不到此方法,JVM 将抛出异常,例如:java.lang.NoSuchMethodError: java.nio.ByteBuffer.flip()Ljava/nio/ByteBuffer;
。)作为奖励问题,我想知道是否使用
--release
为 Java 8 设置的选项会改变答案。但请注意,我不能使用 --release
选项(相当于 Maven <release>
编译器插件选项),因为我希望我的 Maven 项目可以使用 Java 8 和 Java 11 构建。
最佳答案
如果我们使用以下代码并使用 Java 8 和 Java 11 进行编译,我们将得到以下字节码,如运行 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
如您所见,它们都“链接”到 flip()
ByteBuffer
的方法,即使该方法未在 Java 8 中声明。但是,在字节码级别,方法签名包括返回类型。这意味着 JVM 支持可以重载仅返回类型不同的方法的语言,即使 Java 不支持。
Java 11 版本的方法有不同的返回类型,可以在“linked”方法中看到,后面是
()
,其中 Java 8 将返回类型显示为 Ljava/nio/Buffer;
Java 11 将返回类型显示为 Ljava/nio/ByteBuffer;
.当您使用针对 Java 11 运行时库编译的代码并尝试在 Java 8 上运行它时,您会得到
Exception in thread "main" java.lang.NoSuchMethodError: java.nio.ByteBuffer.flip()Ljava/nio/ByteBuffer;
这就是为什么你应该总是指定 引导类路径 指向与目标 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
作为 JEP 247: Compile for Older Platform Versions 的结果,选项在 Java 9 中实现,其中说: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. Thect.sym
file is a ZIP file containing stripped-down class files corresponding to class files from the target platform versions.
关于java - javac 11 应该如何使用 Java 8 目标在更高版本中覆盖方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64868952/