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 8
ByteBuffer
nor Java 11 ByteBuffer
均未在 ByteBuffer
级别声明 flip()
方法。
ByteBuffer
是 Buffer
的直接子 class。两个版本的 Buffer
API 中都声明了 Java 8 Buffer.flip()
and a Java 11 Buffer.flip()
。
- 在Java8源代码中,没有
ByteBuffer.flip()
方法。
- 但是在Java 11 源代码中,
ByteBuffer
重写了Buffer.flip()
方法,如下所示。目的显然是使用协方差,以便在 Java 11 ByteBuffer.flip()
中可以方便地 return 一个 ByteBuffer
而不是 Buffer
。
@Override
public ByteBuffer flip() {
super.flip();
return this;
}
所以重述一下问题:应该 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”到 ByteBuffer
的 flip()
方法,即使没有为 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.
假设我使用的是 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 8
ByteBuffer
nor Java 11ByteBuffer
均未在ByteBuffer
级别声明flip()
方法。 ByteBuffer
是Buffer
的直接子 class。两个版本的Buffer
API 中都声明了 Java 8Buffer.flip()
and a Java 11Buffer.flip()
。- 在Java8源代码中,没有
ByteBuffer.flip()
方法。 - 但是在Java 11 源代码中,
ByteBuffer
重写了Buffer.flip()
方法,如下所示。目的显然是使用协方差,以便在 Java 11ByteBuffer.flip()
中可以方便地 return 一个ByteBuffer
而不是Buffer
。@Override public ByteBuffer flip() { super.flip(); return this; }
所以重述一下问题:应该 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”到 ByteBuffer
的 flip()
方法,即使没有为 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. Thect.sym
file is a ZIP file containing stripped-down class files corresponding to class files from the target platform versions.