目次
要約
- 例外は異常系の通知メカニズム。通常フローには使わないのが原則です。
- Checked(
Exception
系)と Unchecked(RuntimeException
系)を用途で使い分けます。 try-catch-finally
/try-with-resources、マルチキャッチ、再スロー、例外チェーンを正しく活用します。- API設計では意味のあるメッセージ、適切な例外型、ログと再スローの重複回避が重要です。
1. 例外の基本
- 例外は発生地点から呼び出し元へ伝播し、どこかで捕捉(catch)されるまで処理が中断されます。
- スタックトレースは発生箇所の手がかりです。むやみに握りつぶさないでください。
try {
risky();
} catch (Exception e) {
e.printStackTrace(); // 実務ではロガーで記録
}
2. Checked と Unchecked の違い
- Checked:
IOException
など。宣言 or 捕捉が必須。外部要因(I/O、DB、ネット)由来が中心。 - Unchecked:
RuntimeException
とそのサブクラス(NullPointerException
,IllegalArgumentException
等)。宣言不要。呼び出し側のバグや前提違反を表現。
指針:リトライや回復が期待できる外部要因はChecked、プログラミングエラーはUnchecked。
3. try-catch-finally の基本
FileInputStream in = null;
try {
in = new FileInputStream("data.txt");
// 読み取り処理
} catch (FileNotFoundException e) {
// ファイルなしのハンドリング
} catch (IOException e) {
// 読み書き失敗
} finally {
if (in != null) try { in.close(); } catch (IOException ignore) {}
}
finally
はリソース解放の最後の砦です(ただし現代は次項のTWR推奨)。
4. try-with-resources(TWR:AutoCloseable)
import java.io.*;
try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
String line = br.readLine();
} catch (IOException e) {
// I/O失敗の対応
}
try (...)
内のオブジェクトは自動的にcloseされます。- 例外が多重発生した場合、サプレスト(抑制)例外は
getSuppressed()
で参照可能。
try (MyRes r = new MyRes()) {
// 本体で例外
} catch (Exception e) {
for (Throwable sup : e.getSuppressed()) {
// 片付け中に起きた副次的な例外
}
}
5. マルチキャッチ/再スロー/throws
try {
doIoAndParse();
} catch (IOException | NumberFormatException e) {
// 似た復旧策をまとめて処理
recover(e);
throw e; // さらなる上位へ再スロー(ログ二重化に注意)
}
throws
で呼び出し側へ委ねる設計も有効です。
void load() throws IOException {
// I/Oは呼び出し側ポリシーで扱ってもらう
}
6. 例外チェーン(causeの連鎖)
try {
callRemote();
} catch (IOException e) {
throw new BusinessException("受注登録に失敗しました", e); // 原因を保持
}
- 元例外をcauseに保持して失跡可能性を確保します(握りつぶし禁止)。
7. カスタム例外の設計
public class BusinessException extends RuntimeException {
private final String code;
public BusinessException(String message) { this(message, null, "E-BIZ-0001"); }
public BusinessException(String message, Throwable cause) { this(message, cause, "E-BIZ-0001"); }
public BusinessException(String message, Throwable cause, String code) {
super(message, cause);
this.code = code;
}
public String code() { return code; }
}
- ドメイン例外は
RuntimeException
継承が一般的。再試行可能性が高いI/O系はException
継承も選択肢です。 - 人間が読めるメッセージ+機械可読なコードの両方を持たせると運用が楽になります。
8. 代表的な標準例外の使い分け
IllegalArgumentException
:不正な引数IllegalStateException
:オブジェクト状態が前提を満たさないNullPointerException
:null前提違反(Objects.requireNonNull
推奨)UnsupportedOperationException
:未サポート機能NoSuchElementException
:要素なしIOException
:I/O全般TimeoutException
/InterruptedException
:並行処理の中断・タイムアウト
9. ロギングと再スローの重複回避
- 原則:ログは「境界」で一度だけ」(例えばWeb/API層)。
- 下位層では情報を付与して再スローし、最上位で記録するか、下位で記録したら上位では記録しないポリシーを統一します。
e.printStackTrace()
は開発中限定。実運用はロガー利用(java.util.logging
等)。
private static final java.util.logging.Logger LOG =
java.util.logging.Logger.getLogger(App.class.getName());
try {
process();
} catch (BusinessException e) {
LOG.warning(() -> "business error: " + e.getMessage());
throw e; // ここで記録したなら上位は重複記録しない
}
10. 1ファイル実用サンプル(TWR/マルチキャッチ/チェーン)
import java.io.*;
import java.util.Objects;
public class ExceptionDemo {
public static void main(String[] args) {
try {
int total = loadAndSum("numbers.txt");
System.out.println("sum = " + total);
} catch (BusinessException e) {
// アプリ境界で一度だけログ・ハンドリング
e.printStackTrace(); // 実運用はロガー
System.err.println("code=" + e.code() + ", msg=" + e.getMessage());
}
}
// I/O + パース:復旧困難な外部要因(Checked)と入力不正(Unchecked)を分離
static int loadAndSum(String path) {
Objects.requireNonNull(path, "path");
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
int sum = 0;
String line;
int row = 0;
while ((line = br.readLine()) != null) {
row++;
try {
sum += Integer.parseInt(line.trim());
} catch (NumberFormatException e) {
// 入力不正:文脈情報を付けてラップ(Unchecked)
throw new BusinessException("数値として解釈できません row=" + row + " value=[" + line + "]", e, "E-PARSE-1001");
}
}
return sum;
} catch (FileNotFoundException e) {
// 外部要因:ユーザー向けの意味のあるメッセージでラップ
throw new BusinessException("ファイルが見つかりません: " + path, e, "E-IO-404");
} catch (IOException e) {
// I/O一般:原因を保持して上位へ
throw new BusinessException("ファイルの読み取りに失敗しました: " + path, e, "E-IO-0002");
}
}
// ドメイン例外(Unchecked)
public static class BusinessException extends RuntimeException {
private final String code;
public BusinessException(String msg) { this(msg, null, "E-BIZ-0001"); }
public BusinessException(String msg, Throwable cause) { this(msg, cause, "E-BIZ-0001"); }
public BusinessException(String msg, Throwable cause, String code) { super(msg, cause); this.code = code; }
public String code() { return code; }
}
}
numbers.txt
の各行に整数が並ぶ想定です。- 行の一部が不正でもどの行が原因かを例外メッセージで把握できます。
ベストプラクティス要点
- 例外は異常系のみに使用。通常の分岐は制御構文で表現します。
- Checked=外部要因、Unchecked=前提違反の住み分けを徹底します。
- TWRで確実にクローズ。多重例外は
getSuppressed()
で追跡します。 - 意味のあるメッセージ+原因(cause)保持。握りつぶさず上位の境界で一度だけログ。
- APIはthrows方針を明文化し、呼び出し側が対処しやすい設計にします。