信頼度ランク
| S | 公式ソース確認済み |
| A | 成功実績多数・失敗例少数 |
| B | 賛否両論 |
| C | 動作未確認・セキュリティリスク高 |
| Z | 個人所感 |
Java 11の「直接実行」— なぜコンパイル不要でjava HelloWorld.javaが動くのか
Java 11で追加されたJEP 330(ソースファイル起動モード)の仕組みを根本から解説。なぜ.classファイルが作られないのか、なぜファイル名がpublicクラス名と一致しなくていいのかも明らかにする。
一言結論
java Hello.javaで直接実行できるのは、メモリ上でコンパイルして即実行する仕組みのため。.classを作らないのでjavacのファイル名規則が適用されない。ただし1ファイル限定。
Java 11で変わったこと
Java 11より前は、Javaプログラムを動かすのに必ず2ステップ必要だった:
javac HelloWorld.java # ステップ1: コンパイル → HelloWorld.class が生成される
java HelloWorld # ステップ2: 実行
Java 11からは、1ステップで動く:
java HelloWorld.java # これだけでOK
試してみよう:
// Hello.java
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
$ java Hello.java
Hello, World!
.classファイルが生成されていない。なぜ?
JEP 330:ソースファイル起動モード
これは JEP 330(Launch Single-File Source-Code Programs) という仕様でJava 11に追加された機能だ。
JEPとは「Java Enhancement Proposal」、Javaへの機能追加提案書のことだ。JEP 330のモチベーションはシンプルで:
Pythonや Ruby のように、1ファイルのプログラムを手軽に動かせるようにしたい
python script.py # Pythonはこれだけで動く
ruby script.rb # Rubyも同じ
java script.java # Java 11以降、同じように書ける
学習やちょっとしたスクリプト目的で、コンパイルのステップを省けるようにした。
裏で何が起きているのか
java HelloWorld.java を実行したとき、内部ではこういうことが起きている:
1. javaコマンドが引数を確認(.java で終わっている → ソースファイル起動モード)
2. ソースコードをメモリ上でコンパイル
3. コンパイル結果(バイトコード)をメモリに保持
4. そのままJVMで実行
5. 終了(.classファイルはディスクに書き出さない)
ポイントは 「メモリ上でコンパイル」 だ。従来のjavacはディスクに.classファイルを書き出すが、このモードではメモリ内だけで完結する。
従来の2ステップ:
javac Hello.java
→ Hello.class(ディスクに書き出す)
java Hello
→ Hello.classをディスクから読み込んで実行
Java 11のソースファイル起動モード:
java Hello.java
→ メモリ上でコンパイル
→ そのままメモリ上で実行
→ ディスクには何も残らない
なぜファイル名がpublicクラス名と一致しなくていいのか
前の記事(javacとjavaコマンドの仕組み)で説明した通り、通常は:
public class Foo を定義するなら、ファイル名は Foo.java でなければならない
という規則がある。これはjavacがファイルを効率よく探すための規則だ。
ではソースファイル起動モードでは?
// Greeting.java というファイル名なのに
public class Hello { // クラス名は Hello
public static void main(String[] args) {
System.out.println("Hello!");
}
}
$ java Greeting.java
Hello! # ← 動く!
ファイル名はGreeting.java、クラス名はHello、でも動く。
なぜか? javacを使っていないから だ。
通常のコンパイルフローでは、javacが「Fooクラスが必要」→「Foo.javaを探す」という処理をする。だからファイル名=クラス名という規則が必要だった。
しかしソースファイル起動モードでは、javaコマンドが 直接ファイルを指定して読む。javacが「ファイルを名前で探す」処理を一切行わないので、ファイル名とクラス名が一致していなくても関係ない。
通常(javac使用):
javacが「クラス名→ファイル名」で検索する
→ ファイル名 = クラス名 という規則が必要
ソースファイル起動モード(javac不使用):
javaが直接「このファイルを実行せよ」と受け取る
→ ファイル名は何でもよい
では、どのクラスのmainが実行されるのか
ファイル名とクラス名が一致しないなら、どのクラスを起動するか?
答えは ファイルの中で最初に書かれたクラスのmain だ:
// Test.java
class First {
public static void main(String[] args) {
System.out.println("First の main"); // ← これが実行される
}
}
class Second {
public static void main(String[] args) {
System.out.println("Second の main");
}
}
$ java Test.java
First の main
ファイルの先頭のクラスのmainが呼ばれる。これは仕様で定められている。
制約:1ファイルしか使えない
このモードには重要な制約がある。
他のソースファイルを参照できない:
# Main.java が Helper.java を参照している場合
$ java Main.java
# エラー: Helper.java を読み込めない
ソースファイル起動モードは「1ファイルで完結するプログラム」専用だ。複数ファイルにまたがるプロジェクトでは使えない。
ただし、コンパイル済みのクラス(.class / JAR)は参照できる:
# lib/commons.jar を使うプログラムを直接実行する
$ java -cp lib/commons.jar Main.java
クラスパスに指定されたコンパイル済みクラスは使える。ただし、実行するファイル自体はクラスパスに含まれていてはいけない。
いつ使うのか
✅ 向いている場面:
- ちょっとしたコードを動作確認したいとき
- 小さなユーティリティスクリプト(ログ変換、ファイル整形など)
- 学習・実験(javacを意識せずにJavaを試せる)
- シェルスクリプトの代替として
❌ 向かない場面:
- 複数ファイルで構成されたプロジェクト
- 本番環境での利用(毎回コンパイルが走るのでパフォーマンスに不利)
- ビルドツール(Maven/Gradle)を使うプロジェクト
シバン(shebang)と組み合わせる
Unix系OSでは、シバンと組み合わせるとさらにシェルスクリプトに近い書き方ができる:
#!/usr/bin/java --source 21
// greet.java
public class Greet {
public static void main(String[] args) {
System.out.println("Hello, " + args[0] + "!");
}
}
$ chmod +x greet.java
$ ./greet.java World
Hello, World!
Javaファイルをそのままスクリプトとして実行できる。
まとめ
java Hello.java = Java 11のソースファイル起動モード(JEP 330)
仕組み = メモリ上でコンパイルして即実行(.classは作らない)
ファイル名の制約なし = javacを使わないので「ファイル名=クラス名」規則が不要
実行されるmain = ファイルの先頭に書かれたクラスのmain
制約 = 1ファイル完結のみ(他の.javaファイルは参照不可)
用途 = スクリプト的な素早い実行・学習向け
「.classファイルを作らない」という一点が、ファイル名規則の解放につながっている。javacが「ファイルを名前で探す」という処理を行わないから、名前の一致が不要になる。仕組みを知ると、なぜそういう動作になるのかがすんなり理解できる。