如何用Java 11生成代码,但要针对Java 8及更高版本?

zar*_*tra 15 java javac cross-compiling java-11

我正在开发一个小型库,出于明显的原因,我想使用所有Java 11功能(我现在猜不到的模块除外)来生成代码,但是我希望该库与Java 8及更高版本兼容。

当我尝试这个:

javac -source 11 -target 1.8 App.java
Run Code Online (Sandbox Code Playgroud)

我收到以下消息:

warning: source release 11 requires target release 11
Run Code Online (Sandbox Code Playgroud)

...当我查看字节码时,我看到该类的版本为0x37(Java 11):

$ xxd App.class
00000000: cafe babe 0000 0037 ...
Run Code Online (Sandbox Code Playgroud)

Java 8无法加载它:

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.access$100(URLClassLoader.java:74)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:369)
    at java.net.URLClassLoader$1.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)
Run Code Online (Sandbox Code Playgroud)

人们如何提供这种兼容性?我对所有构建工具都开放。

对我来说,将高级语言(Java)转换为低级(字节码)似乎很容易。在我看来,当高级语言更改时,低级语言应保持不变。这就是为什么我认为这是可能的。

更新

伙计们,我认为这个答案不能与Move to OpenJDK-11相同,而是可以在Java 8中进行编译,因为OP询问如何继续生成具有Java 8功能的代码,但要针对Java 11(这是著名的向后兼容性)。我的问题是相反的:我想用Java 11生成代码,但要针对Java8。我在提出问题之前研究该主题时遇到了这个问题。我发现它不适用于我的情况。

另一个问题是否可以将Java 8代码编译为可在Java 7 JVM运行,但确实与我的问题类似,但是在2013年有人问过,字节码显然在Java 7和Java 8之间发生了变化。

自Java 8以来,我认为字节码变化不大,这就是为什么我问这个问题。

Buu*_*man 12

不,您不能将 Java 11 源代码编译为 Java 8 二进制文件。

javac方面,该-source参数不能超过更大的-target参数。

因此,如果您想生成 Java 8 二进制文件,您的源代码应该用 Java 8(或更早版本)编写。如果您不使用任何 Java 11 语言功能,那么您的源代码基本上已经在 J​​ava 8 中,所以这应该不是什么大问题。

请注意,您仍然可以使用 JDK 11 将 Java 8 源代码编译为 Java 8 二进制文件。JDK 版本可以大于源和/或目标版本。

注意:javac 文档没有说明-source参数必须小于或等于-target参数。但是,有很多非官方文档。例如, /sf/answers/648290891/

据我所知,实际上也没有一个反例可以让这种情况发挥作用。


Abr*_*bra 6

尽管在javadoc的javadoc中没有看到任何明确的内容,但我认为您只能为-source和-target选项声明相同的版本。Java 8不支持Java 11功能,尽管情况恰恰相反,较高的Java版本可以运行在较低版本中编译的代码。因此,我认为不可能将编写为Java 11的代码编译为可在Java 8中运行。


Hol*_*ger 6

虽然从理论上讲,使用复杂的工具可以将为JDK 11编译的类转换为JDK 8,但这并非微不足道。二进制级别有重大更改。

首先,JDK 11引入了嵌套类型,从而在访问private内部/外部类的成员时无需生成综合访问器方法。当然,在旧版本中,此类访问将失败。

它还引入了动态常量,尽管我不知道Java语言是否在任何地方都利用了该功能。这主要用于将来的版本。

然后,从JDK 9开始,使用Java 8中不存在的invokedynamic引用来编译字符串连接java.lang.invoke.StringConcatFactory

可以使用的功能是private接口中的方法,它在Java 9中作为语言功能引入,但是已经在Java 8的二进制级别上进行了处理。

Java 8也将无法处理模块定义,但是我想,它们将被忽略。


Gho*_*ica 5

我在这里可能是错的,但至少到目前为止,javac 并不打算以这种方式使用。

这里有点猜测:您可以尝试查看是否--release 8 --target 8有效(不提供--source 11参数)。

但我怀疑这会奏效。我认为 javac 不支持接受 N 源代码功能,并将其向后编译到早期目标版本。

当然,编译器可以了解将 N 个源代码转换为 (Nm) 个字节代码所需的转换。但这会使编译器变得更加复杂,并且每个版本都会增加这一点。它还会给测试工作增加巨大的成本。我怀疑编译器维护者是否愿意购买。这真的不是一个广泛的用例。

所以,我知道的唯一“解决方案”:分支和双重维护。为了让事情保持合理,我会简单地保留一个 Java 8 版本,也许是 Java 11 的一个版本。

  • 二进制级别有显着变化。首先,JDK 11 引入了嵌套类型,它消除了在访问内部/外部类的“私有”成员时生成合成访问器方法的需要。当然,这种访问在旧版本中会失败。它还引入了动态常量,尽管我不知道 Java 语言是否在任何地方利用了该特性。然后,从 JDK 9 开始,使用 `invokedynamic` 来编译字符串连接,它指的是 Java 8 中不存在的 `java.lang.invoke.StringConcatFactory`。编译类的转换是可能的,但并非微不足道。 (3认同)

Tag*_*eev 5

有一个叫做工具的Jabel创建由@bsideup,它可以让你做到这一点。它伪装成一个注解处理器,所以它可以被插入到 javac 中。但是,它不进行任何实际的注释处理。相反,在编译过程中,它侵入了 javac 内部结构,并使其相信在创建匿名类中的var、diamond等新特性,甚至文本块switch 表达式等新特性都可以在 Java 8 中使用。

这是没有任何保证的hacky解决方案,因此请谨慎使用。