main クラスの全体像
Java アプリは「エントリポイント」である main メソッドから始まります。main を持つクラス(便宜上「main クラス」)は、起動時に必要な初期化を行い、コマンドライン引数を受け取り、アプリの「最初の一歩」を指揮します。役割は「全てを書く」ことではなく、準備と振り分けに徹すること。実際のロジックは別クラスへ委ね、main クラスは「起動の配電盤」として小さく保つのが基本です。
main メソッドの仕様と正しい定義
正しいシグネチャと配置
main は必ず public static void main(String[] args) の形で定義し、どのクラスにも自由に置けます。起動時に JVM がこのシグネチャを探して呼び出します。
public class App {
public static void main(String[] args) {
System.out.println("Hello");
}
}
Javapublic: JVM から見える必要があるstatic: インスタンス生成なしで呼ぶためvoid: 戻り値は使われない(終了コードは別手段)String[] args: コマンドライン引数を受け取る
引数の受け取り
起動時に与えた文字列が args に順番どおり入ります。不足してもコンパイルは通るため、存在チェックが必要です。
public static void main(String[] args) {
if (args.length == 0) {
System.err.println("usage: App <name>");
System.exit(1); // 失敗の終了コード
}
System.out.println("Hi, " + args[0]);
}
Javamain クラスの責務(重要ポイントの深掘り)
責務の境界を明確にする
main クラスは次の「準備と分岐」に集中します。
public class App {
public static void main(String[] args) {
var cfg = Config.load(); // 設定読み込み
var service = new Service(cfg); // 依存の組み立て(DI)
var exit = new Cli(service).run(args); // 引数に応じて処理を委譲
System.exit(exit); // 終了コードで成否を伝える
}
}
Java- 入力(引数・環境変数・設定ファイル)の読み込み
- 依存の組み立て(サービスやリポジトリの生成)
- 実処理クラスへの「起動モードの振り分け」
- 成否の終了コードを返す
この「起動配電盤」設計により、ロジックはテスト可能な別クラスへ隔離されます。
例外の入口・出口を整える
main は「最後の防波堤」です。予期せぬ例外をエラーメッセージと終了コードに変換して、原因を失踪させないようにします。
public static void main(String[] args) {
int code = 0;
try {
code = new Cli(new Service(Config.load())).run(args);
} catch (Exception e) {
System.err.println("fatal: " + e.getMessage());
e.printStackTrace(System.err); // 重要ならスタックも
code = 1;
} finally {
System.exit(code);
}
}
Java分離の型:main は薄く、ロジックは外へ
CLI パーサへ委譲して見通しを確保
引数解釈を main に書くと肥大化します。専用クラスへ移し、テストしやすくします。
final class Cli {
private final Service svc;
Cli(Service svc) { this.svc = svc; }
int run(String[] args) {
if (args.length == 0) return usage();
switch (args[0]) {
case "sum": return doSum(args);
case "list": return doList(args);
default: return usage();
}
}
private int usage() { System.err.println("usage: sum|list"); return 1; }
private int doSum(String[] args) { /* 引数チェック→svc呼び出し→結果表示 */ return 0; }
private int doList(String[] args) { /* ... */ return 0; }
}
Java依存の組み立てを「1箇所」に集約
設定・接続情報・キャッシュなどの初期化は main 直下のファクトリへ。後から差し替えやすくなります。
final class Bootstrap {
static Service buildService() {
var cfg = Config.load();
var repo = new Repo(cfg.dbUrl());
return new Service(repo, cfg);
}
}
public static void main(String[] args) {
var svc = Bootstrap.buildService();
System.exit(new Cli(svc).run(args));
}
Java終了コード・標準入出力・スレッドの扱い
終了コードで外側に成否を伝える
System.exit(0) は成功、0以外は失敗(慣例)。バッチやシェルから起動される場合、終了コードは重要な契約です。
int exit = taskSucceeded ? 0 : 2; // 2は「入力エラー」など用途別に決める
System.exit(exit);
Java標準入出力の基本
System.out は通常出力、System.err はエラー。ユーザー向けの結果は out、失敗やヘルプは err へ分けると運用が楽になります。
System.out.println("result: " + value);
System.err.println("usage: App <args>");
Javaスレッドの起動と終了
メインスレッドが終わるとプロセスは終了します(非デーモンスレッドが残っていれば待機)。バックグラウンドを使うなら、終了条件と join を設計しましょう。
var t = new Thread(() -> doWork());
t.start();
t.join(); // 終了を待つ(例外処理も忘れずに)
Javaよくあるバッドパターンと回避
main が「何でも屋」になる
ファイルI/O、ネットワーク、計算、フォーマット、全部を main に書くと巨大メソッド化します。「配電盤」に徹し、詳細はクラス分割。
例外を握りつぶす
catch (Exception e) {} のような空キャッチは致命的。メッセージと終了コードに変換し、原因連鎖(e)を保持する。
終了コードを返さない
失敗でも System.exit(0) や何も返さないと、外部から成功に見えてしまう。用途別にコードを決め、統一する。
テスト不能な設計
main に直接ロジックが書かれているとテストが困難。Cli.run(String[]) のような純粋メソッドに落として、ユニットテスト可能にする。
例題で身につける:最小から配電盤へ
例 1: 最小の main と引数チェック
public class Hello {
public static void main(String[] args) {
if (args.length < 1) {
System.err.println("usage: Hello <name>");
System.exit(1);
}
System.out.println("Hello, " + args[0]);
System.exit(0);
}
}
Java例 2: main は薄く、CLI へ委譲
public class App {
public static void main(String[] args) {
var svc = new Service(Config.load());
int exit = new Cli(svc).run(args);
System.exit(exit);
}
}
Javafinal class Service {
private final Config cfg;
Service(Config cfg) { this.cfg = cfg; }
int sum(int... xs) { return java.util.Arrays.stream(xs).sum(); }
}
final class Cli {
private final Service svc;
Cli(Service svc) { this.svc = svc; }
int run(String[] args) {
if (args.length == 0) return usage();
if ("sum".equals(args[0])) return sumCmd(args);
return usage();
}
private int sumCmd(String[] args) {
try {
var xs = java.util.Arrays.stream(java.util.Arrays.copyOfRange(args, 1, args.length))
.mapToInt(Integer::parseInt).toArray();
System.out.println(svc.sum(xs));
return 0;
} catch (NumberFormatException e) {
System.err.println("invalid number: " + e.getMessage());
return 2;
}
}
private int usage() { System.err.println("usage: App sum <ints...>"); return 1; }
}
Java仕上げのアドバイス(重要部分のまとめ)
main クラスは「起動の配電盤」。設定読み込み、依存の組み立て、引数による振り分け、終了コードの決定だけに集中し、業務ロジックは別クラスへ委譲する。例外は最後の防波堤として捕捉し、メッセージと終了コードで外へ正しく伝える。標準出力と標準エラーを使い分け、スレッドやリソースのライフサイクルを明確にする——この型が身につけば、main は常に小さく、アプリはテストしやすく、運用に強くなります。
