torutkのブログ

ソフトウェア・エンジニアのブログ

Java 21で(ちょびっと)スクリプト風なプログラミング

OpenJDK 21でお手軽なJavaプログラミング(JEP 445, 430)

2023年9月にリリースされるOpenJDK 21では、Previewの位置付け(正式な機能としての搭載ではなく、今後のバージョンで正式搭載する機能の事前仕様・実装を仮搭載し、広く使ってもらいフィードバックを得て改善し正式機能を目指す)ですが、次の機能が入っています。

JEP 445: Unnamed Classes and Instance Main Methods (Preview)

プログラム実行時の入り口(エントリポイント)となるmainメソッドを、クラス定義、static定義、引数定義しで記述

JEP 430: String Templates (Preview)

文字列の中に、変数や式を記述し実行時に変数の値や式の評価に置き換えるテンプレート機能

JEP 445

これまでJavaを実行する時のエントリポイントとなるmainメソッドを定義するには、クラスを定義し、そしてpublic staticなメソッドmainを引数定義付き定義する必要がありました。これを、mainメソッドについてはクラス定義をせずにトップレベルでstatic宣言無しに記述できるようにするものです。(詳細はもっと複雑です)

void main() {
    System.out.println("Hello, Java 21 world");
}

とはいえ、Javaはクラス無しでは実行できないので、コンパイル時にクラス(名前なしのパッケージの名前なしクラス->ファイル名に基づきクラスが生成される模様)が作られます。

JDK 21でのコンパイルと実行

JDK 21が利用できる環境である事を確認

% java --version
openjdk 21-ea 2023-09-19
OpenJDK Runtime Environment (build 21-ea+34-2500)
OpenJDK 64-Bit Server VM (build 21-ea+34-2500, mixed mode, sharing)

JEP 445はプレビュー機能なので、--enable-previewオプションを指定してコンパイル

% javac --enable-preview Hello.java                            
エラー: --enable-previewは-sourceまたは--releaseとともに使用する必要があります

おっと、エラーになりました。--releaseオプションに21を指定して再度コンパイルします。

% javac --enable-preview --release 21 Hello.java
ノート: Hello.javaはJava SE 21のプレビュー機能を使用します。
ノート: 詳細は、-Xlint:previewオプションを指定して再コンパイルしてください。

コンパイルできたかを確認します。

% ls
Hello.class Hello.java

では、実行します。

% java Hello                                    
エラー: メイン・クラスHelloのロード中にLinkageErrorが発生しました
    java.lang.UnsupportedClassVersionError: Preview features are not enabled for Hello (class file version 65.65535). Try running with '--enable-preview'

おっと、プレビュー機能は実行時も--enable-previewオプションが必要です。

MacBook level1 % java --enable-preview Hello     
Hello, Java 21 world.

無事実行できました。

mainメソッドは、従来は public static void main(String args) と、staticメソッドで引数にString配列を要求していましたが、このJEP 445では、

  • privateではないstatic void main(String args)
  • privateではないstatic void main()
  • privateではないvoid main(String[] args)
  • privateではないvoid main()

のいずれかが定義されているとそれをエントリポイントとして呼び出して実行します。(複数定義するときは上述の順番で先に見つかったmainが実行)

JEP 445補足
  • 無名クラスは無名パッケージに属するので、package文を記述するとコンパイルエラーに
  • 無名パッケージは無名モジュールに属する
JEP 430

文字列の中に、変数の値を入れたい、メソッドの実行結果(戻り値)を入れたい、といったときの簡単な機構が用意されました。

String person = "Tom";
String message = STR."Hello, \{person}."

STRは、Template Processorと呼ばれ、STRの後にドットに続けてテンプレート表現を記述します。テンプレート表現では、ダブルクォートで囲み、中に文字列と、バックスラッシュに波括弧で挟んだ中にJavaの式を記述します。式の中には、変数、メソッド呼び出しなどを記述できます。 このテンプレート表現は、文字列リテラルとは違い、中にダブルクォートをエスケープせずにダイレクトに記述してもOKでした。

String message = STR."Hello, Java \{System.getProperty("java.version")} world";

また、テンプレート表現は、波括弧の間が式になるので、ここに複数行に渡るJavaの式を記述することが可能です。 ダブルクォート3連で囲む複数行の文字列リテラルの書き方も可能です。

さらに、このSTRはimport文なしに使用可能です。

従来の文字列に変数や式の値を入れる方法

従来では、文字列同士を+演算子で結合して生成するとか、

String message = "Hello, " + person + ".";

Stringクラスのformatメソッドで、C言語のprintf風な書式文字列と適用する変数を指定するとか、

String message = String.format("Hello, %s.", person);

MessageFormatクラスのformatメソッドで、変数を埋め込みます。

String message = MessageFormat.format("Hello, {0}.", person);

StringBuilderを使う方法もありますが、割愛します。

JDK 21の開発環境

IntelliJ IDEA 2023.2.1 は、まだOpenJDK 21に対応していないので、JEP445のようにクラス定義がないトップレベルにメソッドを定義すると、エディターがエラーを訴えてきます。この状態では、Javaのサポート機能(補完など)が十分に動くことができません。インデントがうまくいかず、JEP445のコードの編集には耐えられないです。

これを無視することにしても、ビルドツールにGradleを使用していると、GradleもJDK 21に対応していないので、Gradleでビルドがエラーになってしまいます。

VSCodeのエディタも同様にJEP445の記述がエディタ上でエラー扱いとなっています。コード補完が効かず、ただこちらはインデントが崩れるまでには至りませんでした。

ということで、素のエディタが欲しくなってしまいます。

コンパイルせずに実行

OpenJDK 11から、単一のファイルだけで完結するJavaソースファイルをコンパイルせずにjavaコマンドで直接実行する機能が導入されています。

C:\work> java --enable-preview Hello.java
java --enable-preview Hello.java
エラー: --enable-previewは--sourceとともに使用する必要があります

おっと、エラーになってしまいました。--sourceオプションを付けて再度実行します。

C:\work> java --enable-preview --source 21 Hello.java
ノート: Hello.javaはJava SE 21のプレビュー機能を使用します。
ノート: 詳細は、-Xlint:previewオプションを指定して再コンパイルしてください。
Hello, Java SE 21 JEP445 world.