例外処理の要点
内容の確認日: 2026-10-04
この分野では、例外が「どこで発生し、どの catch で捕まり、何が出力されるか」を追う問題と、例外処理の書き方がコンパイルできるかを判断する問題が出ます。finally と return の組み合わせや、try-with-resources の close の順番は、特によく問われます。
出題項目は「例外処理の仕組みとチェック例外、非チェック例外、エラーの違い」「try-catch 文による例外処理」「try-with-resources 文による例外処理」「multi-catch 句の使用」「カスタム例外の作成」の 5 つです。
例外の種類
例外のクラスは Throwable を頂点に、次のように分かれます。
・Error: JVM のメモリ不足 (OutOfMemoryError) や、終わりのない再帰によるスタックのあふれ (StackOverflowError) など、プログラムで回復できない重大な問題
・Exception のうち RuntimeException とそのサブクラス: 非チェック例外。NullPointerException・ArithmeticException・IllegalArgumentException など
・それ以外の Exception のサブクラス: チェック例外。IOException・ClassNotFoundException など
チェック例外を投げる可能性のあるメソッドを呼ぶときは、try-catch で捕まえるか、自分のメソッドにも throws を書いて呼び出し元に任せるか、どちらかをしないとコンパイルエラーになります。非チェック例外とエラーには、この義務はありません。
import java.io.IOException;public class Main { static void load() throws IOException { throw new IOException("読み込み失敗"); } public static void main(String[] args) { load(); }}javac Main.java
コンパイルエラー (9 行目): チェック例外 IOException を catch も throws もしていない
Integer.parseInt("abc") が投げる NumberFormatException は、IllegalArgumentException のサブクラスで、非チェック例外です。
try-catch-finally の流れ
try ブロックの途中で例外が発生すると、残りの処理は飛ばされ、例外の型に合う catch ブロックに移ります。catch は上から順に判定され、最初に合ったもの 1 つだけが実行されます。そのため、サブクラスの例外の catch を、スーパークラスの catch より後ろに書くと、到達できない catch としてコンパイルエラーです。
public class Main { public static void main(String[] args) { try { int[] a = new int[1]; a[2] = 1; } catch (RuntimeException e) { System.out.println("Runtime"); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("Index"); } }}javac Main.java
コンパイルエラー (8 行目): ArrayIndexOutOfBoundsException は、先に書いた RuntimeException の catch で必ず捕まるので到達できない
同じように、try ブロックの中で発生する可能性のないチェック例外を catch に書くと、コンパイルエラーになります (Exception や非チェック例外の catch は書けます)。
finally ブロックは、例外が発生してもしなくても、try や catch の中で return しても、必ず実行されます。ただし、return で返す値は return 文を実行した時点で決まっているので、finally の中で変数を変えても戻り値は変わりません。
public class Main { static int test() { int x = 1; try { return x; } finally { x = 2; System.out.println("finally: x = " + x); } } public static void main(String[] args) { System.out.println("戻り値: " + test()); }}java Main finally: x = 2 戻り値: 1
catch ブロックの中で新しい例外が発生した場合、同じ try に付いた他の catch では捕まりません。finally を実行した後、例外は呼び出し元に伝わります。
public class Main { public static void main(String[] args) { try { Object o = "text"; Integer n = (Integer) o; System.out.println("A"); } catch (ClassCastException e) { System.out.println("B"); throw new IllegalStateException("catch の中で発生"); } catch (IllegalStateException e) { System.out.println("C"); } finally { System.out.println("D"); } }}java Main B D
実行時に例外が発生: IllegalStateException
try-with-resources
try (リソースの宣言) { ... } の形の try-with-resources 文を使うと、AutoCloseable を実装したリソースの close が自動で呼ばれます。主なルールは次のとおりです。
・リソースは宣言した順に初期化され、try ブロックを抜けるときに逆の順番で close される
・close は catch・finally より前に呼ばれる
・try ブロックの中で return しても、メソッドを抜ける前に close される
・Java SE 9 からは、try の外で宣言した変数を try (r) のように指定できる。ただし final か、実質的に final (代入し直していない) な変数に限る
class Resource implements AutoCloseable { private final String name; Resource(String name) { this.name = name; System.out.println("open " + name); } @Override public void close() { System.out.println("close " + name); }}public class Main { public static void main(String[] args) { try (Resource a = new Resource("A"); Resource b = new Resource("B")) { System.out.println("処理"); throw new RuntimeException("失敗"); } catch (RuntimeException e) { System.out.println("catch: " + e.getMessage()); } finally { System.out.println("finally"); } }}java Main open A open B 処理 close B close A catch: 失敗 finally
try ブロックで例外が発生した後、close でも例外が発生した場合、catch に渡されるのは try ブロックの例外です。close の例外は抑制された例外として、getSuppressed() で取り出せます。
class Faulty implements AutoCloseable { @Override public void close() throws Exception { throw new Exception("close で失敗"); }}public class Main { public static void main(String[] args) { try (Faulty f = new Faulty()) { throw new IllegalStateException("本体で失敗"); } catch (Exception e) { System.out.println(e.getMessage()); System.out.println(e.getSuppressed()[0].getMessage()); } }}java Main 本体で失敗 close で失敗
multi-catch 句
catch (IllegalArgumentException | IndexOutOfBoundsException e) のように | で区切ると、複数の例外を 1 つの catch で捕まえられます (multi-catch)。次の 2 つの制限があります。
・継承関係にある例外は並べられない (スーパークラス側だけで足りるため)
・例外の変数は暗黙的に final で、別の値を代入できない
public class Main { public static void main(String[] args) { try { Integer.parseInt("x"); } catch (NumberFormatException | IllegalArgumentException e) { System.out.println("変換できません"); } }}javac Main.java
コンパイルエラー (5 行目): NumberFormatException は IllegalArgumentException のサブクラスなので、multi-catch に並べられない
カスタム例外
Exception や RuntimeException を継承すると、独自の例外クラス (カスタム例外) を作れます。Exception を継承すればチェック例外、RuntimeException を継承すれば非チェック例外になります。コンストラクタで super(message) を呼ぶと、getMessage() でそのメッセージを取り出せます。
class InsufficientBalanceException extends Exception { InsufficientBalanceException(String message) { super(message); }}class Wallet { private int balance = 500; void pay(int amount) throws InsufficientBalanceException { if (amount > balance) { throw new InsufficientBalanceException("残高不足: " + (amount - balance) + " 円足りません"); } balance -= amount; }}public class Main { public static void main(String[] args) { Wallet wallet = new Wallet(); try { wallet.pay(300); wallet.pay(300); System.out.println("支払い完了"); } catch (InsufficientBalanceException e) { System.out.println(e.getMessage()); } }}java Main 残高不足: 100 円足りません
1 回目の支払いで残高は 200 円になり、2 回目で例外が発生します。InsufficientBalanceException はチェック例外なので、pay には throws が、呼び出し側には try-catch が必要です。
試験でよくある引っかけ
チェック例外の扱い忘れ
チェック例外を投げるメソッドを呼ぶときは、
try-catchかthrowsが必要です。どちらもないとコンパイルエラーです。catchの順番スーパークラスの例外の
catchを先に書くと、後ろのサブクラスのcatchに到達できずコンパイルエラーです。finallyと戻り値tryでreturn x;した後にfinallyでxを変えても、戻り値は変わりません。catchの中の例外catchの中で発生した例外は、同じtryの他のcatchでは捕まりません。finallyの後に呼び出し元へ伝わります。closeの順番try-with-resources のリソースは、宣言と逆の順番で、
catch・finallyより前にcloseされます。multi-catch の継承関係
FileNotFoundException | IOExceptionのように継承関係にある例外は並べられません。
問題で確認する
この分野の問題を、解説つきで読めます (登録不要)。
出典・参考
- Oracle University 日本「Java SE 17 Programmer I (1Z0-825-JPN) 試験」
- Java 言語仕様 (Java SE 17) 11「Exceptions」
- Java 言語仕様 (Java SE 17) 14.20.3「try-with-resources」
- The Java Tutorials「Exceptions」
コード例は、試験の対象バージョンで実際にコンパイル・実行して結果を確認しています。