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 で前倒しに検証して、誤用を避ける。
