Javaコンパイル
はじめに
Javaコンパイラ(javac)は、人間が読めるソースコード(.java)をJVMが実行できるバイトコード(.class)に変換します。本章では、コンパイルプロセスの各フェーズとバイトコードの構造を解説します。
コンパイルの全体像
┌─────────────────────────────────────────────────────────────────────┐
│ Javaコンパイルの流れ │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ソースコード (.java) │
│ │ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 1. 字句解析 (Lexical Analysis) │ │
│ │ ソースコードをトークンに分解 │ │
│ │ "public class Foo" → [PUBLIC] [CLASS] [IDENTIFIER:Foo] │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 2. 構文解析 (Syntax Analysis / Parsing) │ │
│ │ トークン列から抽象構文木(AST)を構築 │ │
│ │ 文法エラーを検出 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 3. 意味解析 (Semantic Analysis) │ │
│ │ 型チェック、シンボル解決、アノテーション処理 │ │
│ │ 型エラー、未定義変数などを検出 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 4. 中間コード生成・最適化 │ │
│ │ 定数畳み込み、デッドコード除去など │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 5. バイトコード生成 (Code Generation) │ │
│ │ ASTからJVMバイトコードを生成 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ↓ │
│ クラスファイル (.class) │
│ │
└───────────────────────────── ────────────────────────────────────────┘
各フェーズの詳細
1. 字句解析(Lexical Analysis)
ソースコードを意味のある最小単位(トークン)に分解します。
// ソースコード
public class Hello {
public static void main(String[] args) {
System.out.println("Hello");
}
}
トークン列:
[KEYWORD:public] [KEYWORD:class] [IDENTIFIER:Hello] [LBRACE]
[KEYWORD:public] [KEYWORD:static] [KEYWORD:void] [IDENTIFIER:main]
[LPAREN] [IDENTIFIER:String] [LBRACKET] [RBRACKET] [IDENTIFIER:args] [RPAREN]
[LBRACE]
[IDENTIFIER:System] [DOT] [IDENTIFIER:out] [DOT] [IDENTIFIER:println]
[LPAREN] [STRING:"Hello"] [RPAREN] [SEMICOLON]
[RBRACE]
[RBRACE]
2. 構文解析(Syntax Analysis)
トークン列から抽象構文木(AST: Abstract Syntax Tree)を構築します。
CompilationUnit
│
ClassDeclaration
(name: Hello)
│
MethodDeclaration
(name: main)
│
MethodInvocation
/ │ \
receiver method arguments
│ │ │
FieldAccess println StringLiteral
(System.out) ("Hello")
3. 意味解析(Semantic Analysis)
型の整合性やシンボルの解決を行います。
// 型エラーの例
int x = "hello"; // エラー: String を int に代入できない
// 未定義シンボルの例
System.out.println(undefinedVar); // エラー: 変数が定義されていない
// 正しいコード
String message = "hello";
System.out.println(message); // OK: 型が一致
| チェック項目 | 例 |
|---|---|
| 型の互換性 | int x = "string" → エラー |
| 変数の定義 | 未定義変数の参照 → エラー |
| メソッドの存在 | 存在しないメソッド呼び出し → エラー |
| アクセス修飾子 | private メンバへの外部アクセス → エラー |
| 例外処理 | checked例外の未処理 → エラー |
4. コード生成(Code Generation)
ASTからJVMバイトコードを生成します。
// ソースコード
public int add(int a, int b) {
return a + b;
}
// 生成されるバイトコード
public int add(int, int);
Code:
0: iload_1 // 引数 a をスタックにロード
1: iload_2 // 引数 b をスタックにロード
2: iadd // スタック上の2値を加算
3: ireturn // 結果を返す
バイトコードの構造
クラスファイルフォーマット
┌─────────────────────────────────────────────────────────────────────┐
│ クラスファイル構造 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Magic Number: 0xCAFEBABE │ │
│ │ (Javaクラスファイルの識別子) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Version: minor_version, major_version │ │
│ │ (Java 21 = major version 65) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Constant Pool │ │
│ │ ・クラス名、メソッド名、フィールド名 │ │
│ │ ・文字列リテラル、数値定数 │ │
│ │ ・メソッド参照、フィールド参照 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Access Flags │ │
│ │ (public, final, abstract など) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ This Class / Super Class / Interfaces │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Fields │ │
│ │ (フィールド定義) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Methods │ │
│ │ (メソッド定義 + バイトコード) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Attributes │ │
│ │ (ソースファイル名、アノテーションなど) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
バイトコード命令の種類
| カテゴリ | 命令例 | 説明 |
|---|---|---|
| ロード/ストア | iload, astore | ローカル変数とスタック間の転送 |
| 算術演算 | iadd, imul, isub | 整数演算 |
| 型変換 | i2l, d2i | 型変換 |
| オブジェクト操作 | new, getfield | オブジェクト生成、フィールドアクセス |
| スタック操作 | dup, pop, swap | スタック操作 |
| 制御フロー | if_icmpeq, goto | 条件分岐、ジャンプ |
| メソッド呼び出し | invokevirtual, invokestatic | メソッド呼び出し |
| 戻り値 | ireturn, areturn | メソッドからの復帰 |
バイトコードの確認方法
# javap でバイトコードを逆アセンブル
javap -c MyClass.class
# 詳細表示(定数プール含む)
javap -v MyClass.class
# private メンバも表示
javap -p -c MyClass.class
javac オプション
よく使うオプション
# 基本的なコンパイル
javac HelloWorld.java
# 出力先ディレクトリを指定
javac -d out/ src/HelloWorld.java
# クラスパスを指定
javac -cp lib/dependency.jar src/Main.java
# ソースバージョンとターゲットバージョン
javac --source 21 --target 21 Main.java
# すべての警告を表示
javac -Xlint:all Main.java
# 非推奨APIの使用警告
javac -deprecation Main.java
# デバッグ情報を含める
javac -g Main.java
# エンコーディング指定
javac -encoding UTF-8 Main.java
警告オプション(-Xlint)
# 全ての警告を有効化
javac -Xlint:all Main.java
# 特定の警告のみ
javac -Xlint:unchecked,deprecation Main.java
# 特定の警告を無効化
javac -Xlint:all,-serial Main.java
| 警告タイプ | 説明 |
|---|---|
| unchecked | ジェネリクスの未チェック変換 |
| deprecation | 非推奨APIの使用 |
| rawtypes | raw型の使用 |
| serial | Serializable で serialVersionUID がない |
| finally | finally ブロックが正常に完了しない |
| fallthrough | switch の case が fall through |
コンパイル時の最適化
javacはいくつか の最適化を行います(ただし、主要な最適化はJIT時に行われます)。
定数畳み込み(Constant Folding)
// ソースコード
int x = 1 + 2 + 3;
// コンパイル後(定数が計算済み)
int x = 6;