- はじめに
- 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
ファイルが複数のモジュールにまたがるソースコードプログラムを実行することはできない