一角獣は夜に啼く

ただの日記です。

思ってることとか考えたこととか適当に書きます。 主にソフトウェア開発の話題を扱う 「ひだまりソケットは壊れない」 というブログもやってます。

Java のバージョン間クロスコンパイル時の標準ライブラリについて (JDK 8 で Java 7 向けにコンパイルする場合など)

追記 (2017-10-21)

Java SE 9 のリリースにより、より便利な --release フラグを使えるようになりました! 下記ページを参考にしてください。

vividcode.hatenablog.com

元の内容

時代は Java 8 ですが、ライブラリなどはまだ Java 7 向けだったり Java 6 向けにビルドすることも多いと思います。

そんなとき、javac-source オプションや -target オプションを使ってターゲットバージョンを指定しますね。 Gradle を使っていれば次のような感じですね。

sourceCompatibility = 1.7
targetCompatibility = 1.7

ここで、何も気にせず JDK 8 でコンパイルしていると Java SE 7 環境には存在しない Java API のメソッドを使っていることに気付かず、Java SE 7 環境で実行するとエラーが発生する、ということが起こりえます。 そういうことが起こらないように、コンパイル時には対象となる Java バージョンに応じた標準ライブラリを見ましょう、という話です。

ブートストラップクラスパスの設定に関する警告

上のような設定を書いて javac コマンド (Gradle を使っていたら gradle build コマンド) でコンパイルを実行すると、次のような警告が発生すると思います。

警告: [options] ブートストラップ・クラスパスが-source 1.7と一緒に設定されていません

これは、ターゲットとなる Java のバージョンとして 1.7 が指定されているにもかかわらず、コンパイル時に参照される標準ライブラリが指定されていない *1 という警告です。 コンパイル自体は問題なく完了しますが、例えば Java SE 8 で導入された Java API のメソッドを使用している場合、コンパイルはできても Java SE 7 環境で実行するとメソッドがなくてエラーになる、ということが起こりえるため警告が出されるようです。

実行時ではなくコンパイル時にエラーが出るように、ターゲットとなる Java バージョンの Java API を参照してコンパイルするように変更すべきです。

Oracle によるドキュメントでは次のような感じで書かれています。

You must specify the -bootclasspath option to specify the correct version of the bootstrap classes (the rt.jar library). If not, then the compiler generates a warning:

javac -source 1.7 OldCode.java
warning: [options] bootstrap class path not set in conjunction with -source 1.7

If you do not specify the correct version of bootstrap classes, then the compiler uses the old language rules (in this example, it uses version 1.7 of the Java programming language) combined with the new bootstrap classes, which can result in class files that do not work on the older platform (in this case, Java SE 7) because reference to nonexistent methods can get included.

javac

javac コマンドを直接実行する場合、-bootclasspath を適切に設定します。

Eclipse の場合

Eclipse の場合、プロジェクトのプロパティを変更してビルド時の標準ライブラリを指定できます。

Java Build Path」 の 「Libraries」 の中を見ると、デフォルトではワークスペースのデフォルト JRE のライブラリが使われるような設定になっていると思いますが、それを変更してやればよいです。 指定するバージョンの JRE (または JDK) は予めインストールしておく必要があります。

f:id:nobuoka:20141114021854p:plain

Gradle の場合

Gradle を使う場合は、上のページにあるように次のようなコードをビルドスクリプトに追加してやればよいです。 対象となるバージョンの JDK をインストールしておいて、そのパスを環境変数 JDK7_HOME に入れておけばビルド時にその中の rt.jar などが参照されるようになります。

tasks.withType(JavaCompile) {
    doFirst {
        if (sourceCompatibility == '1.7' && System.env.JDK7_HOME != null) {
            options.fork = true
            options.bootClasspath = "$System.env.JDK7_HOME/jre/lib/rt.jar"
            options.bootClasspath += "$File.pathSeparator$System.env.JDK7_HOME/jre/lib/jsse.jar"
            // use the line above as an example to add jce.jar 
            // and other specific JDK jars
        }
    }
}

とはいえ面倒な感じですし普段の開発は Eclipse なりなんなりでするでしょうから、そっちで設定を行って Gradle は素の状態でいいのではないかなーと思う次第です。

*1:つまりコンパイルに使用されている JDK に含まれるライブラリが参照される? ここら辺はちょっとあやふやです。