如何在 Java 11 中编写代码,但针对 Java 8 及更高版本?
How to write code in Java 11, but target Java 8 and above?
我正在开发一个小型库,出于显而易见的原因,我想使用所有 Java 11 个功能(我现在猜测的模块除外)编写代码,但我希望该库兼容Java 8 及以上。
当我尝试这个时:
javac -source 11 -target 1.8 App.java
我收到以下消息:
warning: source release 11 requires target release 11
...当我查看字节码时,我看到 class 的版本是 0x37
(Java 11):
$ xxd App.class
00000000: cafe babe 0000 0037 ...
而Java8无法加载:
Exception in thread "main" java.lang.UnsupportedClassVersionError: App has been
compiled by a more recent version of the Java Runtime (class file version 55.0),
this version of the Java Runtime only recognizes class file versions up to 52.0
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:468)
at java.net.URLClassLoader.access0(URLClassLoader.java:74)
at java.net.URLClassLoader.run(URLClassLoader.java:369)
at java.net.URLClassLoader.run(URLClassLoader.java:363)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:362)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:495)
人们如何提供这种兼容性?我对所有构建工具持开放态度。
对我来说,将高级语言(Java)转换为低级(字节码)似乎很容易。在我看来,当高级语言发生变化时,低级语言应该保持不变。这就是为什么我认为这是可能的。
更新
伙计们,我认为这个答案不会重复 ,因为 OP 在那里询问如何继续生成具有 Java 8
功能的代码,但目标是 Java 11(这只是一个著名的向后兼容性)。我的问题是相反的:我想在 Java 11 中生成代码,但目标是 Java 8。我在提出问题之前研究该主题时遇到了这个问题。我发现它不适用于我的情况。
另一个问题Can Java 8 code be compiled to run on Java 7 JVM看起来确实和我的问题很相似,但它是在2013年提出的,字节码明显在Java 7和Java 8之间发生了变化。
自从 Java 8 以来,我认为字节码变化不大,这就是我问这个问题的原因。
尽管我在 javadoc 中没有看到 javac 的任何明确内容,但我认为您只能为两者声明相同的版本-source 和 -target 选项。 Java 11 个功能在 Java8 中不受支持,尽管相反,更高的 java 版本可以 运行 代码在较低版本中编译。所以我认为不可能将写入 Java 11 的代码编译为 运行nable in Java 8.
我在这里可能是错的,但至少到现在为止,javac 不应该以这种方式使用。
这里有点猜测:您可以尝试查看 --release 8 --target 8
是否有效(不提供 --source 11
参数)。
但我怀疑这是否有效。我认为 javac 不支持接受 N 源代码功能,并向后编译到早期的目标版本。
当然,编译器可能知道将 N 源代码转换为 (N-m) 字节代码所需的转换。但这会使编译器变得更加复杂,并且每个版本都会增加它。它还会显着增加 测试 工作的成本。我怀疑编译器维护者是否愿意接受这一点。这真的不是一个广泛的用例。
所以,只有 "solution" 我知道:分支和双重维护。为了保持合理,我会简单地保留一个 Java 8 版本, 可能 一个用于 Java 11.
不,您不能将 Java 11 个源代码编译为 Java 8 个二进制文件。
在javac
术语中,-source
参数不能大于-target
参数。
所以,如果你想生成 Java 8 个二进制文件,你的源代码应该用 java 8(或更早)编写。
如果您不使用任何 Java 11 语言功能,您的资源基本上已经在 Java 8 中,所以这应该不是什么大问题。
请注意,您仍然可以使用 JDK 11 将 Java 8 源代码编译为 Java 8 二进制文件。 JDK 版本 可以 大于源 and/or 目标版本。
注意:javac 文档没有说明 -source
参数必须小于或等于 -target
参数。但是,有很多 non-official 文档。例如,
据我所知,也没有一个 counter-example 真正让这种情况发挥作用。
虽然理论上可以使用复杂的工具将为 JDK11 编译的 类 转换为 JDK8,但这并非易事。二进制级别有显着变化。
首先,JDK 11 引入了 nest 类型,这消除了在访问 inner/outer 类 的 private
成员时生成合成访问器方法的需要。当然,这样的访问在旧版本中会失败。
它还引入了 dynamic constants,但我不知道 Java 语言是否在任何地方利用了该功能。这主要是为了以后的版本。
然后,自 JDK 9 起,字符串连接使用 invokedynamic
引用 java.lang.invoke.StringConcatFactory
进行编译,这在 Java 8.
中不存在
一个可行的功能是接口中的 private
方法,在 Java 9 中作为语言功能引入,但在 Java 8 中已经在二进制级别处理。
Java 8 也无法处理模块定义,但我想,它们会被忽略。
有一个名为 Jabel created by @bsideup, which allows you to do this. It pretends to be an annotation processor so it could be plugged into javac. However, it doesn't do any actual annotation processing. Instead, during the compilation, it hacks into javac internals and makes it believe that newer features like var, diamond in anonymous class creation and even newer ones like text blocks and switch expressions 的工具可以在 Java 8.
中使用
这是没有任何保证的 hacky 解决方案,因此请谨慎使用。
我正在开发一个小型库,出于显而易见的原因,我想使用所有 Java 11 个功能(我现在猜测的模块除外)编写代码,但我希望该库兼容Java 8 及以上。
当我尝试这个时:
javac -source 11 -target 1.8 App.java
我收到以下消息:
warning: source release 11 requires target release 11
...当我查看字节码时,我看到 class 的版本是 0x37
(Java 11):
$ xxd App.class
00000000: cafe babe 0000 0037 ...
而Java8无法加载:
Exception in thread "main" java.lang.UnsupportedClassVersionError: App has been
compiled by a more recent version of the Java Runtime (class file version 55.0),
this version of the Java Runtime only recognizes class file versions up to 52.0
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:468)
at java.net.URLClassLoader.access0(URLClassLoader.java:74)
at java.net.URLClassLoader.run(URLClassLoader.java:369)
at java.net.URLClassLoader.run(URLClassLoader.java:363)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:362)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:495)
人们如何提供这种兼容性?我对所有构建工具持开放态度。
对我来说,将高级语言(Java)转换为低级(字节码)似乎很容易。在我看来,当高级语言发生变化时,低级语言应该保持不变。这就是为什么我认为这是可能的。
更新
伙计们,我认为这个答案不会重复 Java 8
功能的代码,但目标是 Java 11(这只是一个著名的向后兼容性)。我的问题是相反的:我想在 Java 11 中生成代码,但目标是 Java 8。我在提出问题之前研究该主题时遇到了这个问题。我发现它不适用于我的情况。
另一个问题Can Java 8 code be compiled to run on Java 7 JVM看起来确实和我的问题很相似,但它是在2013年提出的,字节码明显在Java 7和Java 8之间发生了变化。
自从 Java 8 以来,我认为字节码变化不大,这就是我问这个问题的原因。
尽管我在 javadoc 中没有看到 javac 的任何明确内容,但我认为您只能为两者声明相同的版本-source 和 -target 选项。 Java 11 个功能在 Java8 中不受支持,尽管相反,更高的 java 版本可以 运行 代码在较低版本中编译。所以我认为不可能将写入 Java 11 的代码编译为 运行nable in Java 8.
我在这里可能是错的,但至少到现在为止,javac 不应该以这种方式使用。
这里有点猜测:您可以尝试查看 --release 8 --target 8
是否有效(不提供 --source 11
参数)。
但我怀疑这是否有效。我认为 javac 不支持接受 N 源代码功能,并向后编译到早期的目标版本。
当然,编译器可能知道将 N 源代码转换为 (N-m) 字节代码所需的转换。但这会使编译器变得更加复杂,并且每个版本都会增加它。它还会显着增加 测试 工作的成本。我怀疑编译器维护者是否愿意接受这一点。这真的不是一个广泛的用例。
所以,只有 "solution" 我知道:分支和双重维护。为了保持合理,我会简单地保留一个 Java 8 版本, 可能 一个用于 Java 11.
不,您不能将 Java 11 个源代码编译为 Java 8 个二进制文件。
在javac
术语中,-source
参数不能大于-target
参数。
所以,如果你想生成 Java 8 个二进制文件,你的源代码应该用 java 8(或更早)编写。 如果您不使用任何 Java 11 语言功能,您的资源基本上已经在 Java 8 中,所以这应该不是什么大问题。
请注意,您仍然可以使用 JDK 11 将 Java 8 源代码编译为 Java 8 二进制文件。 JDK 版本 可以 大于源 and/or 目标版本。
注意:javac 文档没有说明 -source
参数必须小于或等于 -target
参数。但是,有很多 non-official 文档。例如,
据我所知,也没有一个 counter-example 真正让这种情况发挥作用。
虽然理论上可以使用复杂的工具将为 JDK11 编译的 类 转换为 JDK8,但这并非易事。二进制级别有显着变化。
首先,JDK 11 引入了 nest 类型,这消除了在访问 inner/outer 类 的 private
成员时生成合成访问器方法的需要。当然,这样的访问在旧版本中会失败。
它还引入了 dynamic constants,但我不知道 Java 语言是否在任何地方利用了该功能。这主要是为了以后的版本。
然后,自 JDK 9 起,字符串连接使用 invokedynamic
引用 java.lang.invoke.StringConcatFactory
进行编译,这在 Java 8.
一个可行的功能是接口中的 private
方法,在 Java 9 中作为语言功能引入,但在 Java 8 中已经在二进制级别处理。
Java 8 也无法处理模块定义,但我想,它们会被忽略。
有一个名为 Jabel created by @bsideup, which allows you to do this. It pretends to be an annotation processor so it could be plugged into javac. However, it doesn't do any actual annotation processing. Instead, during the compilation, it hacks into javac internals and makes it believe that newer features like var, diamond in anonymous class creation and even newer ones like text blocks and switch expressions 的工具可以在 Java 8.
中使用这是没有任何保证的 hacky 解决方案,因此请谨慎使用。