Javaの例外を連鎖的に処理する方法!causeとsuppressedの使い方を徹底解説
生徒
「Javaの例外処理で、複数のエラーが関係してるときって、どうやって原因をたどればいいんですか?」
先生
「良いポイントですね。Javaではcauseやsuppressedという仕組みを使って、例外を連鎖的に扱えます。」
生徒
「連鎖的って、例外が例外を引き起こすってことですか?」
先生
「そのとおり!それでは、Javaの例外のcauseとsuppressedの使い方を詳しく見ていきましょう!」
1. Javaの例外処理とは?
Javaの例外処理は、プログラム中に発生するエラーをキャッチして、適切に処理するための仕組みです。基本的にはtry、catch、finallyを使って記述します。たとえばファイル読み込み処理やネットワーク通信、数値計算などで問題が発生したときに、例外を使って安全にプログラムの異常を扱います。
しかし現実のプログラムでは、単一の原因で例外が発生するとは限らず、ある例外が別の例外の原因になっていたり、複数の例外が関係することもあります。そんなときに役立つのがcauseとsuppressedという仕組みです。
2. 例外を連鎖的に追跡する「cause」プロパティ
causeは、ある例外が発生したときに「もともとの原因となった例外」を保持するためのプロパティです。これはThrowableクラスのgetCause()メソッドで取得できます。
例えば、ファイルの読み込み中にIOExceptionが発生し、それをラップしてRuntimeExceptionを投げたい場合、causeを使って例外の原因を関連づけることができます。
public class CauseExample {
public static void main(String[] args) {
try {
try {
throw new IllegalArgumentException("元の例外です");
} catch (IllegalArgumentException e) {
throw new RuntimeException("ラップされた例外です", e);
}
} catch (RuntimeException e) {
System.out.println("メッセージ: " + e.getMessage());
System.out.println("原因: " + e.getCause());
}
}
}
メッセージ: ラップされた例外です
原因: java.lang.IllegalArgumentException: 元の例外です
このように、causeを使うことで、最終的にどのようなエラーが何に起因していたのかをたどることができます。
3. 例外を補足して記録する「suppressed」プロパティ
suppressedは、主にtry-with-resources構文の中で使われます。複数のリソースを自動でクローズする際に、最初の例外を保持しつつ、他の例外も「抑制された(suppressed)」形で記録する仕組みです。
以下のようなコードで、リソースのクローズ時に複数の例外が出た場合に確認できます。
class MyResource implements AutoCloseable {
private String name;
MyResource(String name) { this.name = name; }
@Override
public void close() throws Exception {
throw new Exception(name + "を閉じるときに例外発生");
}
}
public class SuppressedExample {
public static void main(String[] args) throws Exception {
try (MyResource res1 = new MyResource("リソース1");
MyResource res2 = new MyResource("リソース2")) {
throw new Exception("処理中に例外発生");
} catch (Exception e) {
System.out.println("メインの例外: " + e.getMessage());
for (Throwable suppressed : e.getSuppressed()) {
System.out.println("抑制された例外: " + suppressed.getMessage());
}
}
}
}
メインの例外: 処理中に例外発生
抑制された例外: リソース2を閉じるときに例外発生
抑制された例外: リソース1を閉じるときに例外発生
getSuppressed()メソッドで、クローズ処理などで発生した副次的な例外をすべて取得できます。
4. try-with-resources構文とsuppressedの関係
try-with-resourcesは、AutoCloseableインターフェースを実装したリソースを、自動でクローズしてくれる構文です。複数のリソースがある場合でも、クローズの順番に従って例外を適切に処理します。
例えば、ファイル、DB接続、ネットワークリソースなどを一度に扱う場合でも、安全にリソースを解放できます。その際、1つの例外をスローしつつ、それ以外の例外をsuppressedとして記録するのが、この構文の強みです。
5. causeとsuppressedの違いと使い分け
- cause: ある例外が他の例外によって引き起こされたときの「原因」を表す。
- suppressed: 主な例外の陰に隠れた、同時発生した他の例外を保持。
causeは基本的に「ラップ」する形式で使われますが、suppressedはtry-with-resourcesなどの自動クローズ時に「副次的な例外」を記録するために使われます。
6. Java例外のデバッグ時に知っておきたいポイント
Javaでエラーや例外の原因を正確に追跡するためには、getCause()やgetSuppressed()を活用することが非常に重要です。IDE(IntelliJ IDEAやEclipse)でスタックトレースを確認するときも、原因のチェーンや抑制された例外が表示されるため、デバッグがしやすくなります。
また、ログ出力やエラーレポートの中でも、causeとsuppressedをログに含めておくことで、運用中の不具合をスムーズに調査できるようになります。
7. suppressedを手動で追加する方法
addSuppressed()メソッドを使えば、自分でThrowableに抑制された例外を追加することもできます。これはカスタムエラーハンドリングなどで便利です。
public class AddSuppressedExample {
public static void main(String[] args) {
Exception main = new Exception("メインの例外");
Exception sub = new Exception("補足された例外");
main.addSuppressed(sub);
for (Throwable t : main.getSuppressed()) {
System.out.println("抑制された: " + t.getMessage());
}
}
}
抑制された: 補足された例外