Java | 基礎文法:catch

Java Java
スポンサーリンク

catch の全体像

catch は、try ブロックで発生した例外を「受け止めて、意味のある対処をする」ための節です。例外の型ごとに複数の catch を並べ、具体的な回復やメッセージ出力、再スロー(別の例外へ包み直し)を行います。重要なのは「何を捕まえるか」「どこまで責任を持って対処するか」を明確にし、握り潰しを避けることです。


基本構文と例外オブジェクトの使い方

もっとも基本的な書き方

try {
    int x = Integer.parseInt("123");
    System.out.println("OK: " + x);
} catch (NumberFormatException e) {
    System.err.println("数値に変換できません: " + e.getMessage());
}
Java
  • 例外型: どの失敗に対処するかを型で表します。
  • 例外オブジェクト: e.getMessage()e.printStackTrace() で詳細を取得できます。

具体的な対処を添える

try {
    var br = new java.io.BufferedReader(new java.io.FileReader("input.txt"));
    System.out.println(br.readLine());
    br.close();
} catch (java.io.FileNotFoundException e) {
    System.err.println("ファイルが存在しません: " + e.getMessage());
} catch (java.io.IOException e) {
    System.err.println("読み込み失敗: " + e.getMessage());
}
Java
  • 役割分担: 失敗の種類に応じたメッセージと回復策を用意します。
  • 順序: サブクラス(FileNotFoundException)を先、親クラス(IOException)を後に。

キャッチの順序とマルチキャッチ(重要ポイントの深掘り)

サブクラスを先に並べる

try {
    // I/O処理...
} catch (java.io.FileNotFoundException e) {
    // ここが先
} catch (java.io.IOException e) {
    // 親は後
}
Java
  • 理由: 親を先に書くと、子に到達できずコンパイルエラーになります。

マルチキャッチで簡潔に

try {
    int port = Integer.parseInt(System.getenv("APP_PORT"));
    var server = new java.net.ServerSocket(port);
    System.out.println("start on " + port);
} catch (NumberFormatException | java.io.IOException e) {
    System.err.println("設定不正または起動失敗: " + e.getMessage());
    System.out.println("既定ポート 8080 で再試行します");
}
Java
  • 同一対処: 複数の型を | でまとめて、重複コードを減らします。
  • 型の扱い: 変数 e は共通の最小上位型として扱われます。

握り潰しを避ける書き方と再スロー

何もしない catch は禁物

try {
    dangerous();
} catch (Exception e) {
    // 何もしない → 隠蔽・追跡不能
}
Java
  • 安全策: ログを残す、ユーザー通知、既定値でのフォールバック、または再スローを設計する。

原因を連鎖させて包み直す(ラップ)

try {
    service.process(req);
} catch (java.sql.SQLException e) {
    throw new IllegalStateException("DB処理失敗: reqId=" + req.id(), e);
}
Java
  • ポイント: 新しい例外へ包むときは原因 e を必ず渡す。スタックトレースで根本原因を追えます。
  • 設計: 下位層の具体例外を、上位層の意味ある例外に変換する(層の責務を明確に)。

再スローで責任を上位に委ねる

try {
    risky();
} catch (java.io.IOException e) {
    // ログだけ出して再スロー
    System.err.println("I/O失敗: " + e.getMessage());
    throw e; // 型は同じまま上へ
}
Java
  • 使いどころ: ここで回復しない方針なら、上位の集中ハンドリングへ渡します。

catch のスコープ設計と「最小の塊」

失敗しうる最小範囲だけを try に

String line;
try (var br = java.nio.file.Files.newBufferedReader(java.nio.file.Path.of("a.txt"))) {
    line = br.readLine();
} catch (java.io.IOException e) {
    line = ""; // フォールバック
}
System.out.println(line);
Java
  • 読みやすさ: 大きく囲みすぎると、どこで失敗したかが曖昧になります。
  • 状態管理: try の外で使う変数は外で宣言し、例外時の既定値を決めておく。

try-with-resources とサプレスト例外

自動クローズの中でも catch は活躍する

import java.nio.file.*;
import java.io.*;

public class Reader {
    static String first(Path p) {
        try (var br = Files.newBufferedReader(p)) {
            return br.readLine();
        } catch (IOException e) {
            System.err.println("読込失敗: " + e.getMessage());
            return "";
        }
    }
}
Java
  • 利点: リソースは自動 close。catch は本体の失敗へ集中できます。
  • サプレスト例外: close 時の例外は「suppressed」として付与されるため、ログ時に getSuppressed() の確認が有効です。
for (Throwable t : e.getSuppressed()) {
    System.err.println("suppressed: " + t);
}
Java

よくある落とし穴(重要ポイントの深掘り)

何でもかんでも catch (Exception) はしない

  • 問題: 原因特定が難しく、誤復旧の温床になる。
  • 指針: 想定する失敗だけ個別に捕捉。未知は上位のグローバルハンドラへ。

catch (Throwable) は原則禁止

  • 理由: Error(JVMの致命的エラー)まで拾ってしまい、無理な継続を招く。
  • 例外: フレームワークの最外層でログだけ記録し、即終了する目的なら慎重に。

例外で通常フローを作らない

  • 指針: 前提チェックは if で事前に行い、例外は「本当に異常」だけに使う。

例題で身につける

例 1: 具体的な回復策つきのキャッチ

public class ConfigLoader {
    static java.util.Properties load(String path) {
        var props = new java.util.Properties();
        try (var in = new java.io.FileInputStream(path)) {
            props.load(in);
        } catch (java.io.FileNotFoundException e) {
            System.err.println("設定ファイルが見つからない。デフォルトを使用します。");
            // デフォルト設定を適用
        } catch (java.io.IOException e) {
            throw new IllegalStateException("設定読込失敗: " + path, e);
        }
        return props;
    }
}
Java

例 2: マルチキャッチで簡潔な通知

public class ParseAndConnect {
    public static void main(String[] args) {
        try {
            int port = Integer.parseInt(System.getenv("APP_PORT"));
            var socket = new java.net.Socket("localhost", port);
            System.out.println("connected: " + port);
        } catch (NumberFormatException | java.io.IOException e) {
            System.err.println("起動設定エラーまたは接続失敗: " + e.getMessage());
            System.out.println("ヘルプ: APP_PORT を整数で設定してください。");
        }
    }
}
Java

例 3: 包み直しで層の責務を守る

class Repository {
    User find(String id) {
        try {
            // DB処理...
            return new User(id, "Sato");
        } catch (java.sql.SQLException e) {
            throw new IllegalStateException("DB失敗: id=" + id, e); // インフラ由来→アプリ例外へ
        }
    }
}
Java

仕上げのアドバイス(重要部分のまとめ)

  • 順序: サブクラスを先、親クラスを後。対処が同じならマルチキャッチで簡潔に。
  • 責務: この層で回復するのか、上位へ委ねるのかを明確に。握り潰しはしない。
  • 連鎖: 包み直すときは原因例外を必ず渡し、ログは一箇所に集約。
  • スコープ: 失敗しうる最小範囲だけ try に。外で使う変数は外で宣言し、既定値を用意。
  • 方針: 例外は異常時の手段。通常の分岐は if で前倒しに検証して、誤用を避ける。

タイトルとURLをコピーしました