ひだまりソケットは壊れない

ソフトウェア開発に関する話を書きます。 最近は主に Android アプリ、Windows アプリ (UWP アプリ)、Java 関係です。

まじめなことを書くつもりでやっています。 適当なことは 「一角獣は夜に啼く」 に書いています。

Java SE 9 の javac で過去バージョンをターゲットにするときには --release オプションを使用すると良い

JDKjavac コマンドを使って過去バージョンの Java 言語で書かれたソースコードコンパイルする際には、-source オプションや -target オプションを使用してきました。 (JDK 8 までの話。) このとき、適切なブートストラップクラスパスを設定しなければ、対象バージョンに存在しない Java API を使用していてもビルドが通ってしまう、という問題がありました。

JDK 9 の javac コマンドには --release オプションが追加されました。 今後は (基本的には) このオプションを使用するようにすると良さそうです。

  • ドキュメント : javac

Java SE 9 (Oracle JDK 9) のマイグレーションガイドより

マイグレーションガイドには以下のように書かれています。

If you use the -source and -target options with javac, then check the values that you use. In JDK 9, javac uses a "one plus three back" policy of supporting -source and -target options.

The supported -source/-target values are 9 (the default), 8, 7, and 6 (6 is deprecated, and a warning is displayed when this value is used).

In JDK 8, -source and -target values of 1.5/5 and earlier were deprecated and caused a warning to be generated. In JDK 9, those values cause an error.

>javac -source 5 -target 5 Sample.java 
warning: [options] bootstrap class path not set in conjunction with -source 1.5 
error: Source option 1.5 is no longer supported. Use 1.6 or later. 
error: Target option 1.5 is no longer supported. Use 1.6 or later.

If possible, use the new --release flag instead of the -source and -target options. The --release N flag is conceptually a macro for:

-source N -target N -bootclasspath $PATH_TO_rt.jar_FOR_RELEASE_N

The valid arguments for the --release flag follow the same policy as for -source and -target, one plus three back.

javac can recognize and process class files of all previous JDKs, going all the way back to JDK 1.0.2 class files.

See JEP 182: Policy for Retiring javac -source and -target Options.

Java Platform, Standard Edition Oracle JDK 9 Migration Guide, Release 9

つまり、--release フラグを指定すると、-source オプションと -target オプションを指定し、さらに適切なブートクラスパスの指定も行った状態になります。 これまで、過去バージョンのブートクラスパスを設定するには、過去バージョンの Java 実行環境を取得してその中の rt.jar を指定してやる必要があったので、格段に便利になりました。

試してみた

実際に、以下のようなコードを含む Java のソースファイルをコンパイルしてみました。

public class Main {
    public static void main(String[] args) {
        String test = String.join("", ""); // Java SE 8 で Java API に導入されたメソッド。
    }
}

--release 8 を指定した場合は、問題なくコンパイルされます。

~\java-project> javac --release 8 .\Main.java

--release 7 を指定した場合は、下記のようにエラーになります。

~\java-project> javac --release 7 .\Main.java
.\Main.java:3: エラー: シンボルを見つけられません
        String test = String.join("", "");
                            ^
  シンボル:   メソッド join(String,String)
  場所: クラス String
エラー1個

Java 7 向けにビルドしたけど Java 7 の実行環境で動かすと (Java 7 にない API を使っていて) 例外が発生する、というようなミスが減りそうで最高ですね!

Gradle での使い方

Gradle 4.2.1 現在、JavaCompile タスクjavac--release オプションを指定する方法として特別なメソッドは提供されていません。 通常のコンパイラオプションを指定する方法は使用できるので、通常のコンパイラオプションの指定と同じ方法で --release オプションを指定します。

具体的には CompileOptions#compilerArgs プロパティを使用します。 このドキュメントを読むと、『For example, it is possible to pass the --release option of JDK 9』 とか 『Note that if --release is added then -target and -source are ignored.』 とか書かれています。 --release オプションにもばっちり対応されていますね。

超単純な build.gradle の例を書いておきます。

apply plugin: 'java'

tasks.withType(JavaCompile) {
    options.compilerArgs.addAll(['--release', '8'])
}