
- はじめに
- JEP 458 Launch Multi-File Source-Code Programs
- Using pre-compiled classes
- How the launcher finds source files
- Launch-time operation
- Differences between compilation at compile time versus launch time
はじめに
Java11 では、JEP 330 Launch Single-File Source-Code Programs にて明示的なコンパイル操作を省略し、.java ファイルを java コマンドで直接実行できるようになりました。
例えば、Prog.java ファイルを以下の内容で作成した場合、
class Prog { public static void main(String[] args) { Helper.run(); } } class Helper { static void run() { System.out.println("Hello!"); } }
単純に以下のように実行することができます(オンメモリ上でコンパイルして実行される)。
$ java Prog.java Hello!
JEP 330 では、プログラムのソースコードはすべて1つの .java ファイルに収めなければならないという制限事項があります。
JEP 458 Launch Multi-File Source-Code Programs では、複数の .java ファイルに対しても、明示的なコンパイル操作なく実行を可能にします。
JEP 458 Launch Multi-File Source-Code Programs
あるディレクトリにProg.javaとHelper.javaという2つのファイルがあり、Prog クラスが Helper クラスを参照しているとします。
// Prog.java class Prog { public static void main(String[] args) { Helper.run(); } }
// Helper.java class Helper { static void run() { System.out.println("Hello!"); } }
2つのファイルに分かれていても、以下のように実行することができるようになります。
$ java Prog.java Hello!
java Prog.java を実行すると、Prog クラスがメモリ上でコンパイルされ、main メソッドが呼び出されます。このクラスのコードは Helper クラスを参照しているので、ランチャーはファイルシステム内の Helper.java ファイルを見つけて、そのクラスをメモリ内でコンパイルして実行します。
Using pre-compiled classes
クラス・パスまたはモジュール・パス上のライブラリに依存するプログラムも、ソース・ファイルから起動できます。
あるディレクトリに2つの小さなプログラムと1つのヘルパー・クラス、そしていくつかのライブラリJARファイルがあった場合、
Prog1.java Prog2.java Helper.java library1.jar library2.jar
以下のように --class-path '*' を指定して実行することができます(* はシェルによる展開を避けるために引用符で囲んでいる)。
$ java --class-path '*' Prog1.java $ java --class-path '*' Prog2.java
ライブラリが libs ディレクトリにあった場合は --class-path 'libs/*' のようにクラスパスを指定できます。
モジュールライブラリ(ソースツリーのルートに module-info.java ファイルがある)を利用する場合は以下のように実行します。
$ java -p . pkg/Prog1.java
モジュール化されたJARファイルが libs ディレクトリにある場合は、-p libs のように指定できます。
How the launcher finds source files
java ランチャーは最初の .java ファイルのパッケージ名とファイルシステムの位置からソースツリーの ルートを計算します。
Prog が無名パッケージ、Helper が pkg パッケージ(名前付きパッケージ)に属する場合は以下のように実行できます。
// Prog.java class Prog { public static void main(String[] args) { pkg.Helper.run(); } }
// pkg/Helper.java package pkg; class Helper { static void run() { System.out.println("Hello!"); } }
$ java Prog.java Hello!
Prog が main パッケージに属する場合、
// main/Prog.java package main; class Prog { public static void main(String[] args) { pkg.Helper.run(); } }
// pkg/Helper.java package pkg; class Helper { static void run() { System.out.println("Hello!"); } }
以下のように実行する必要があります。
$ java main/Prog.java Hello!
過去のリリースでは、java a/b/c/Prog.java は a/b/c に Prog.java がある限り、ファイル中の package の宣言に関係なく実行できましたが、本機能追加にてこの動作は変更されます。
Launch-time operation
javaランチャーのソースファイルモードは以下のステップで実行されます。
- ファイルが shebang
#!行で始まる場合、コンパイラに渡されるソースパスは空なので、ステップ4に進む(他のソースファイルはコンパイルされない)。 - ソースツリーのルートとなるディレクトリを計算する
- ソース・コード・プログラムのモジュールを決定する
- ルートに
module-info.javaファイルが存在する場合は、そのモジュール宣言を使用して、ソースツリー内の.javaファイルからコンパイルされたすべてのクラスを含む名前付きモジュールを定義する - もし
module-info.javaが存在しなければ、.javaファイルからコンパイルされたすべてのクラスは名前のないモジュールに格納される
- ルートに
- 最初の
.javaファイル内のすべてのクラスと、場合によっては最初のファイル内のコードから参照されるクラスを宣言している他の.javaファイルをコンパイルし、結果のclassファイルをインメモリーキャッシュに格納する - 初期ファイル
.javaの launch class を決定する- 初期ファイルの最初のトップレベルクラスが標準の
mainメソッド (public static void main(String[])または JEP 463 で定義されているその他の標準mainエントリポイント) を宣言している場合、そのクラスが起動クラスとなる - そうでない場合、初期ファイル内の他のトップレベルクラスが標準の
mainメソッドを宣言しており、ファイル名が同じであれば、そのクラスが起動クラスとなる - そうでない場合、起動クラスは存在せず、ランチャーはエラーを報告して停止する
- 初期ファイルの最初のトップレベルクラスが標準の
- カスタム・クラス・ローダーを使ってメモリ内のキャッシュから起動クラスをロードし、そのクラスの標準の
mainメソッドを呼び出す
Differences between compilation at compile time versus launch time
javac によるコンパイルと、java ランチャーのソースファイルモードで行われるコンパイルにはいくつかの大きな違いがあります。
- ソースファイルモードでは、
.javaファイルにあるクラス宣言は、実行開始前に一度にコンパイルされるのではなく、プログラムの実行中にオンデマンドでインクリメンタルにコンパイルされるため、コンパイル・エラーが発生した場合はプログラムの実行開始後に終了する - リフレクション経由でアクセスされたクラスは、直接アクセスされたクラスと同じ方法でロードされる
- アノテーションプロセッサは無効化され、
--proc:noneがjavacに渡されたときと同じ動作となる .javaファイルが複数のモジュールにまたがるソースコードプログラムを実行することはできない