これは、なにをしたくて書いたもの?
javacコマンドで、--release
というオプションがあることを認識していなかったようなので、少し見ておくことにしました。
これは、JEP 247 Compile for Older Platform Versionsというもののようです。
JEP 247 Compile for Older Platform Versions
JEP 247(Compile for Older Platform Versions)は、Java 9で追加されたものです。
JEP 247: Compile for Older Platform Versions
似たようなオプションに-source
と-target
があります。こちらを使った時の問題点として、-target
で指定したバージョンよりも
新しいAPIを使ってもコンパイルが通ってしまい、実際に古いバージョンのJavaで実行した時に実行時エラーになるということがありました。
Java 9で追加された--release
というオプションを使うことで、指定したバージョンのJavaで使えるAPIの範囲でコンパイルすることが
できるようになります。改良された-source
と-target
という感じですね。
以下はjavac --help
による説明ですが、--release
と-source
、-target
で指定できる値は同じですね。
--release <release> 指定されたJava SEリリースに対してコンパイルします。サポートされているリリース: 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 --source <release>, -source <release> 指定されたJava SEリリースとソースの互換性を保持します。サポートされているリリース: 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 --target <release>, -target <release> 指定されたJava SEリリースに適したクラス・ファイルを生成します。サポートされているリリース: 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21
ちょっと試してみましょう。
環境
今回の環境は、こちら。
$ javac --version javac 21.0.1 $ java --version openjdk 21.0.1 2023-10-17 OpenJDK Runtime Environment (build 21.0.1+12-Ubuntu-222.04) OpenJDK 64-Bit Server VM (build 21.0.1+12-Ubuntu-222.04, mixed mode, sharing)
また、Java 8も利用します。
$ /usr/lib/jvm/java-8-openjdk-amd64/bin/javac -version javac 1.8.0_392 $ /usr/lib/jvm/java-8-openjdk-amd64/bin/java -version openjdk version "1.8.0_392" OpenJDK Runtime Environment (build 1.8.0_392-8u392-ga-1~22.04-b08) OpenJDK 64-Bit Server VM (build 25.392-b08, mixed mode)
最後の方で、Mavenも使います。
$ mvn --version Apache Maven 3.9.6 (bc0240f3c744dd6b6ec2920b3cd08dcc295161ae) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 21.0.1, vendor: Private Build, runtime: /usr/lib/jvm/java-21-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "5.15.0-91-generic", arch: "amd64", family: "unix"
-sourceと-targetで確認する
まずは-source
と-target
で確認してみましょう。
たとえば、List.of
はJava 9で追加されたメソッドです。
こちらを使ったソースコードを作成。
App.java
import java.util.List; public class App { public static void main(String... args) { System.out.println(List.of(1, 2, 3)); } }
$ javac --source 8 --target 8 App.java 警告: [options] ブートストラップ・クラスパスが-source 8と一緒に設定されていません 警告: [options] ソース値8は廃止されていて、今後のリリースで削除される予定です 警告: [options] ターゲット値8は廃止されていて、今後のリリースで削除される予定です 警告: [options] 廃止されたオプションについての警告を表示しないようにするには、-Xlint:オプションを使用します。 警告4個
コンパイルは通ります。
で、実際にJava 8で実行すると実行時エラーになります。
$ /usr/lib/jvm/java-8-openjdk-amd64/bin/java App Exception in thread "main" java.lang.NoSuchMethodError: java.util.List.of(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/List; at App.main(App.java:5)
ちなみに、警告に出ていますが--boot-class-path
(-bootclasspath
)を合わせて指定するとコンパイルエラーにすることができます。
$ javac --source 8 --target 8 --boot-class-path /usr/lib/jvm/java-8-openjdk-amd64/jre/lib/rt.jar App.java 警告: [options] ソース値8は廃止されていて、今後のリリースで削除される予定です 警告: [options] ターゲット値8は廃止されていて、今後のリリースで削除される予定です 警告: [options] 廃止されたオプションについての警告を表示しないようにするには、-Xlint:オプションを使用します。 App.java:5: エラー: シンボルを見つけられません System.out.println(List.of(1, 2, 3)); ^ シンボル: メソッド of(int,int,int) 場所: インタフェース List エラー1個 警告3個
一応、--boot-class-path
を使うことで整合性を取れるようにはできるようですが、設定が多いのとrt.jar
をパス指定する必要があるのが
厄介ですね…。
--releaseオプションを使う
それでは、--release
オプションを使ってみましょう。
先ほどのソースコードを--release
オプションをつけて、Java 8としてコンパイルしてみます。
$ javac --release 8 App.java 警告: [options] ソース値8は廃止されていて、今後のリリースで削除される予定です 警告: [options] ターゲット値8は廃止されていて、今後のリリースで削除される予定です 警告: [options] 廃止されたオプションについての警告を表示しないようにするには、-Xlint:オプションを使用します。 App.java:5: エラー: シンボルを見つけられません System.out.println(List.of(1, 2, 3)); ^ シンボル: メソッド of(int,int,int) 場所: インタフェース List エラー1個 警告3個
今回はしっかりコンパイルエラーになりました。とてもすっきりしますね。rt.jar
を指定しなくてもOKですし。
とはいえ、実際に過去のバージョン向けにコンパイルできていないと意味がありません。こちらも確認しておきましょう。
ソースコードを以下のように変更。
App.java
import java.util.ArrayList; import java.util.List; public class App { public static void main(String... args) { //System.out.println(List.of(1, 2, 3)); List<Integer> list = new ArrayList<>(); list.add(1); list.add(2); list.add(3); System.out.println(list); } }
まずはなにも考えずにコンパイル。
$ javac App.java
Java 8で実行。
$ /usr/lib/jvm/java-8-openjdk-amd64/bin/java App Error: A JNI error has occurred, please check your installation and try again Exception in thread "main" java.lang.UnsupportedClassVersionError: App has been compiled by a more recent version of the Java Runtime (class file version 65.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:756) at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142) at java.net.URLClassLoader.defineClass(URLClassLoader.java:473) 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:418) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:352) at java.lang.ClassLoader.loadClass(ClassLoader.java:351) at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:621)
クラスファイルのバージョンから、これは実行時エラーになります。
では、--release
オプションをつけてコンパイル。
$ javac --release 8 App.java 警告: [options] ソース値8は廃止されていて、今後のリリースで削除される予定です 警告: [options] ターゲット値8は廃止されていて、今後のリリースで削除される予定です 警告: [options] 廃止されたオプションについての警告を表示しないようにするには、-Xlint:オプションを使用します。 警告3個
実行。
$ /usr/lib/jvm/java-8-openjdk-amd64/bin/java App [1, 2, 3]
OKですね。
Maven Compiler Pluginで--releaseオプションを設定する
Javaソースコードを、javacコマンドで直接コンパイルする機会なんてめったにありませんよね。
というわけで、Maven Compiler Pluginでどのように設定するのかも見ておきます。
-source
および-target
オプションの時は、以下のように簡単に指定ができました。
<properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties>
Apache Maven Compiler Plugin – Setting the -source and -target of the Java Compiler
configuration
で設定する場合はこちら。
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.12.1</version> <configuration> <source>8</source> <target>8</target> </configuration> </plugin>
--release
についても、同じように簡単に指定ができます。
<properties> <maven.compiler.release>8</maven.compiler.release> </properties>
Apache Maven Compiler Plugin – Setting the --release of the Java Compiler
configuration
で設定する場合はこちらですね。
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.12.1</version> <configuration> <release>8</release> </configuration> </plugin>
こちらも確認しておきましょう。
pom.xml
の設定。
<properties> <maven.compiler.release>8</maven.compiler.release> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> </properties>
src/main/java/App.java
import java.util.List; public class App { public static void main(String... args) { System.out.println(List.of(1, 2, 3)); } }
$ mvn compile
コンパイルエラーになりました。
[INFO] --- compiler:3.11.0:compile (default-compile) @ hello-world --- [INFO] Changes detected - recompiling the module! :source [INFO] Compiling 1 source file with javac [debug release 8] to target/classes [INFO] ------------------------------------------------------------- [WARNING] COMPILATION WARNING : [INFO] ------------------------------------------------------------- [WARNING] ソース値8は廃止されていて、今後のリリースで削除される予定です [WARNING] ターゲット値8は廃止されていて、今後のリリースで削除される予定です [WARNING] 廃止されたオプションについての警告を表示しないようにするには、-Xlint:オプションを使用します。 [INFO] 3 warnings [INFO] ------------------------------------------------------------- [INFO] ------------------------------------------------------------- [ERROR] COMPILATION ERROR : [INFO] ------------------------------------------------------------- [ERROR] /path/to/src/main/java/App.java:[5,32] シンボルを見つけられません シンボル: メソッド of(int,int,int) 場所: インタフェース java.util.List [INFO] 1 error [INFO] -------------------------------------------------------------
もしくはこちらをコメントアウトして
<properties> <!-- <maven.compiler.release>8</maven.compiler.release> --> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> </properties>
Maven Compiler Pluginの設定を記述。
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.12.1</version> <configuration> <release>8</release> </configuration> </plugin> </plugins> </build>
コンパイル結果は同じなので、省略します。
また、実際にJava 8で動作するようにソースコードを修正して、コンパイルした確認結果についても省略します。
おわりに
javacの--release
オプション(JEP 247 Compile for Older Platform Versions)について、少し調べてみました。
今までIDEの自動生成結果などを頼って以下の記述をしていたことも多かったのですが、release
も活用した方が安全かつ便利だなと
思いましたね。
<properties> <maven.compiler.source>...</maven.compiler.source> <maven.compiler.target>...</maven.compiler.target> </properties>
覚えておきましょう。