コンパイルと実行の全体像
Java のプログラムは「ソースコード(.java)→ バイトコード(.class)→ JVM で実行」という三段構成で動きます。まず javac がソースをコンパイルして OS 非依存のバイトコードを作り、次に java が JVM を起動してそのバイトコードを実行します。これにより、同じ .class や .jar を Windows・macOS・Linux などで再コンパイルなしに動かせます。
コンパイルは「文法チェック+最適化+バイトコード生成」、実行は「クラス読み込み+検証+JIT 最適化+動作」という役割分担です。初心者は「書く→コンパイル→実行」のサイクルを小さく回すことで、エラーの意味や構造を早く体得できます。
最小の流れと基本コマンド
Hello, World(最短レシピ)
// Hello.java
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, Java!");
}
}
Javaターミナル(またはコマンドプロンプト)で次の 2 ステップです。
javac Hello.java # コンパイルして Hello.class を生成
java Hello # クラス名だけを指定して実行(拡張子は付けない)
ファイル名と public class 名は一致させます(Hello.java と public class Hello)。java Hello.class のように拡張子を付けるのは誤りです。実行は「クラス名」、コンパイルは「ファイル名」で覚えると混乱しません。
よくあるつまずき(最初に潰す)
- クラス名とファイル名の不一致:
public classを含むソースは必ず同名のファイルにする。 - カレントディレクトリが違う:
java HelloはHello.classがあるディレクトリで実行する。 - PATH が通っていない:
javacとjavaが見つからない場合は JDK のインストールと PATH 設定を確認。
クラスとファイルの関係(深掘り)
Java クラスは原則 1 ファイル 1 public class です。エントリポイントは public static void main(String[] args) で、JVM は起動時に指定クラスのこのメソッドから実行を開始します。main が static である理由は、インスタンスを作らなくても起動直後に呼べるようにするためです。
同一ファイル内に public でない複数クラスを定義することは可能ですが、可読性と保守性の面で初心者は「クラスごとにファイルを分ける」を徹底しましょう。後でパッケージやビルドツールを使うときに構造がきれいな方が圧倒的に楽です。
パッケージとディレクトリ構造(最初に押さえる)
パッケージはクラスの「住所」です。ファイル先頭に package com.example.app; と書いたら、そのクラスは com/example/app ディレクトリに置きます。
// src/com/example/app/Main.java
package com.example.app;
public class Main {
public static void main(String[] args) {
System.out.println("Pkg demo");
}
}
Javaコンパイルと実行は次のようにします。
# ルート(src)で実行し、出力先を bin に分ける例
javac -d bin src/com/example/app/Main.java
java -cp bin com.example.app.Main
-d はクラスファイルの出力先、-cp(クラスパス)は「クラスやライブラリを探す場所」を JVM に知らせます。
クラスパスと外部ライブラリ(深掘り)
クラスパスは、JVM とコンパイラが「どこにクラス/JAR があるか」を探す検索パスです。標準以外のライブラリ(JAR)を使うときは、コンパイル時と実行時の両方でクラスパスに通す必要があります。
例:外部 JAR を使ってコンパイル・実行
# 例: lib/mylib.jar を使う
javac -cp lib/mylib.jar -d bin src/com/example/App.java
java -cp bin:lib/mylib.jar com.example.App # Unix 系は区切りが :
# Windows の場合
java -cp bin;lib\mylib.jar com.example.App # 区切りが ;
クラスパスを忘れると ClassNotFoundException や NoClassDefFoundError が発生します。コンパイル時(javac)と実行時(java)は別フェーズなので、両方に指定するのがポイントです。
複数ファイル・再利用の例
例:ユーティリティとメインを分ける
// src/com/example/util/MathUtil.java
package com.example.util;
public class MathUtil {
public static int add(int a, int b) {
return a + b;
}
}
Java// src/com/example/app/Main.java
package com.example.app;
import com.example.util.MathUtil;
public class Main {
public static void main(String[] args) {
System.out.println(MathUtil.add(3, 5));
}
}
Javaコンパイルと実行は次の通りです。
javac -d bin src/com/example/util/MathUtil.java src/com/example/app/Main.java
java -cp bin com.example.app.Main
依存関係があっても、パッケージとディレクトリを正しく切っておけばコンパイルは一発で通ります。規模が大きくなったら Gradle/Maven を使いますが、まずは素の javac で構造を理解するのが近道です。
配布形態と JAR 実行(深掘り)
JAR は「クラスとリソースの束」です。エントリポイント(Main-Class)をマニフェストに書くことで、java -jar だけで起動できる配布形態になります。
マニフェストでエントリポイント指定
# クラスをコンパイル
javac -d bin src/com/example/app/Main.java
# マニフェストを作成(改行は最後にも入れる)
echo "Main-Class: com.example.app.Main" > MANIFEST.MF
# JAR 化(bin 配下のクラスをまとめる)
jar cfm app.jar MANIFEST.MF -C bin .
実行は次の通りです。
java -jar app.jar
JAR にしておくと、配布も起動も単純になります。外部ライブラリがある場合は「実行時にクラスパスを通す」か、「影 JAR(fat JAR)」で内包する方法が一般的です。
実行時引数と終了コード(運用で効く知識)
main(String[] args) の args にはコマンドライン引数が入ります。成功・失敗を OS に伝えるときは System.exit(コード) を使い、慣例的に 0 が成功、0 以外が失敗です。
例:引数検証と終了コード
import java.nio.file.*;
import java.io.IOException;
import java.util.List;
public class CountLines {
public static void main(String[] args) {
if (args.length != 1) {
System.out.println("使い方: java CountLines <path>");
System.exit(1);
}
try {
List<String> lines = Files.readAllLines(Path.of(args[0]));
System.out.println("lines=" + lines.size());
} catch (IOException e) {
System.out.println("読み取り失敗: " + e.getMessage());
System.exit(2);
}
}
}
Javaこの構成は「入力を検証→処理→失敗時は異なる終了コード」で、シェルスクリプトや CI から扱いやすくなります。本体ロジックは標準ライブラリや別メソッドに委ね、main は薄く保つのが設計の基本です。
仕上げのアドバイス(重要ポイントの深掘り)
小さく「書く→コンパイル→実行」を回し、エラーをその場で潰す癖をつけましょう。クラス名とファイル名の一致、パッケージとディレクトリ構造、クラスパスの指定――この 3 点を正しく運べば、Java のビルドは一気に安定します。JAR 化まで通せるようになると、学習から実運用への橋渡しがスムーズに進みます。
