1. 概述

在这个教程中,我们将了解Java 9的新命令行选项--release。使用--release N选项运行的Java编译器会自动生成与Java版本N兼容的类文件。我们将讨论这个选项如何与现有的编译命令行选项-source-target相关联。

2. 需要--release选项的情况

为了理解--release选项的必要性,考虑这样一个场景:我们需要使用Java 8编译代码,并希望生成的类文件能兼容Java 7。

在Java 9之前,通过设置-source-target选项可以实现这一点,其中:

  • -source: 指定编译器接受的Java版本
  • -target: 指定生成的类文件的Java版本

假设编译后的程序只使用当前平台(在这种情况下是Java 8)特有的API。那么,无论-source-target选项设置为何值,编译后的程序都无法在像Java 7这样的早期版本上运行。

此外,为了在Java 8及以下版本工作,我们需要同时添加-bootclasspath选项。

为了简化跨编译问题,Java 9引入了新的--release选项来简化过程。

3. 与-source-target选项的关系

根据JDK定义--release N可以扩展为:

  • 对于N < 9:-source N -target N -bootclasspath <从N获取的文档化API>

  • 对于N >= 9:-source N -target N --system <从N获取的文档化API>

  • -bootclasspath: 一个以分号分隔的目录列表、JAR存档和ZIP存档,用于搜索启动类文件

  • --system: 替换Java 9及更高版本的系统模块位置

对于Java版本N < 9,这些API包括从位于jre/lib/rt.jar和其他相关jar中的启动类文件。

对于Java版本N >= 9,这些API包括从jdkpath/jmods/目录下的Java模块获取的启动类文件。

首先,我们创建一个示例类并使用Java 9中重写的flip方法:

import java.nio.ByteBuffer;

public class TestForRelease {

    public static void main(String[] args) {
        ByteBuffer bb = ByteBuffer.allocate(16);
        bb.flip();
        System.out.println("Baeldung: --release option test is successful");
    }
}

4.1. 使用现有的-source-target选项

使用-source-target选项值为8在Java 9中编译代码:

/jdk9path/bin/javac TestForRelease.java -source 8 -target 8 

结果成功,但伴有警告:

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

1 warning

现在,我们在Java 8上运行我们的代码:

/jdk8path/bin/java TestForRelease

我们看到这失败了:

Exception in thread "main" java.lang.NoSuchMethodError: java.nio.ByteBuffer.flip()Ljava/nio/ByteBuffer;
at com.corejava.TestForRelease.main(TestForRelease.java:9)

如我们所见,这并不是我们期望的结果,尽管我们在--release-target选项中指定了8。尽管编译器应该考虑到这一点,但事实并非如此。

让我们详细了解一下。

在Java 9之前,Buffer类包含flip方法:

public Buffer flip() {
    ...
 }

而在Java 9中,ByteBuffer(它扩展了Buffer)重写了flip方法:

@Override
public ByteBuffer flip() {
    ...
}

当这个新方法在Java 9上编译并在Java 8上运行时,由于两个方法返回类型不同,运行时方法查找使用描述符失败,导致错误:

Exception in thread "main" java.lang.NoSuchMethodError: java.nio.ByteBuffer.flip()Ljava/nio/ByteBuffer;
at com.corejava.TestForRelease.main(TestForRelease.java:9)

在编译过程中,我们得到了先前忽略的警告。这是因为Java编译器默认使用最新的API进行编译。即使我们指定了-source-target为8,编译器仍然使用了Java 9的类,因此我们的程序无法在Java 8上运行。

因此,我们必须向Java编译器传递另一个名为-bootclasspath的命令行选项来选择正确的版本。

现在,让我们使用-bootclasspath选项重新编译相同的代码:

/jdk9path/bin/javac TestForRelease.java -source 8 -target 8 -Xbootclasspath ${jdk8path}/jre/lib/rt.jar

这次,编译结果成功,而且没有警告。

现在,我们在Java 8上运行我们的代码,看到它是成功的:

/jdk8path/bin/java TestForRelease 
Baeldung: --release option test is successful

虽然现在跨编译工作正常,但我们需要提供三个命令行选项。

4.2. 使用--release选项

现在,让我们使用--release选项编译相同的代码:

/jdk9path/bin/javac TestForRelease.java —-release 8

同样,这次编译也成功了,没有警告。

最后,当我们用Java 8运行代码时,我们看到它是成功的:

/jdk8path/bin/java TestForRelease
Baeldung: --release option test is successful

我们看到,使用--release选项非常直接,因为javac在内部设置了正确的-source-target-bootclasspath值。

5. 在Maven编译插件中的使用

通常,我们会使用Maven或Gradle等构建工具,而不是命令行javac工具。所以,在这一部分,我们将看看如何在Maven编译插件中应用--release选项。

首先,我们来看看如何使用现有的-source-target选项:

<plugins>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.12.1</version>
        <configuration>
            <source>1.8</source>
            <target>1.8</target>
        </configuration>
    </plugin>
 </plugins>

接下来,这是如何使用--release选项的方法:

<plugins>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.12.1</version>
        <configuration>
            <release>1.8</release>
        </configuration>
    </plugin>
 </plugins>

尽管行为与之前描述的相同,但我们传递这些值给Java编译器的方式有所不同。

6. 总结

在这篇文章中,我们了解了--release选项及其与现有-source-target选项的关系。然后,我们看到了如何在命令行和Maven编译插件中使用这个选项。

最后,我们看到新的--release选项在跨编译时需要的输入选项更少。因此,建议在可能的情况下优先使用它,而不是-target-source-bootclasspath选项。