CodeGym /コース /JAVA 25 SELF /finally と throw: 後処理と例外のスロー

finally と throw: 後処理と例外のスロー

JAVA 25 SELF
レベル 11 , レッスン 3
使用可能

1. finally ブロックの概要

ファイル・ネットワーク接続・データベースなどのリソースを扱うときは、処理中にエラーが起きても必ずそれらをクローズまたは解放できることが重要です。Java にはそのための専用ブロック finally があります。

finally はどのように動作するか?

finally ブロックは、try-catch-finally 構文の一部です。finally 内のコードは常に実行されます(記述されていれば)。例外の有無に関係なく実行されます。たとえ try 内で return した場合や例外がスローされた場合でも、finally は実行されます(コンピュータの電源断や System.exit(0) による強制終了などの特殊なケースを除く)。

構文:

try {
    // 例外が発生しうるコード
} catch (ExceptionType e) {
    // エラー処理
} finally {
    // このコードは必ず実行される
}

try {
    System.out.println("処理開始");
    int result = 10 / 0; // ここでエラーが発生する
    System.out.println("結果: " + result);
} catch (ArithmeticException e) {
    System.out.println("エラー: ゼロで除算");
} finally {
    System.out.println("このコードは必ず実行されます");
}

実行結果:

処理開始
エラー: ゼロで除算
このコードは必ず実行されます

何が起きているのか?

  1. try で 2 つの数を割ろうとしてエラーになります。
  2. 割り算でエラーが発生した場合は catch がそれを処理します。
  3. しかし! いずれの場合も finally は実行され、コンソールにメッセージを出力します。

2. catch なしの finally

構成は次の3通りがあります:

  • 完全形: try-catch-finally
  • finally なし: try-catch
  • catch なし: try-finally

3 つ目は、エラーの捕捉と処理を上位のメソッドに任せる場合に使います。ただし finally ブロックは、特定のコードを必ず実行させるために必要です:

  • ファイル、ネットワーク接続、データベースのクローズ。
  • あらゆるリソースの解放(たとえばロック)。
  • ロギング(操作終了の記録)。

例:

try {
    System.out.println("数を割ります");
    int result = 10 / 0;   // エラー!
    System.out.println("結果: " + result);
} finally {
    System.out.println("finally ブロックが実行されました");
}

結果:

数を割ります
finally ブロックが実行されました
Exception in thread "main" java.lang.ArithmeticException: / by zero

finally が実行されないのはいつ?

ほぼ常に実行されます。例外は次のような場合です:

  1. System.exit(0) を使ってプログラムを強制終了した。
  2. finally が実行されるスレッドを強制的に停止した。
  3. コンピュータの電源が切れた。

3. throw 演算子: 自分で例外をスローする方法

Java は(ゼロ除算や配列の範囲外アクセスなど)自動で例外を「投げる」ことがあります。しかし、自分で「これはエラーだ。続行できない」と明示したい場面もあります。そのために Java には throw 演算子があります。

たとえ話: お店で消費期限切れの商品を見つけたら苦情を「投げ」ますよね。コードでも同様に、何かおかしければ例外をスローします。

throw の構文

throw new ExceptionType("エラーメッセージ");

ExceptionTypeThrowable を継承する任意のクラスです(通常は Exception または RuntimeException)。括弧内のメッセージは、何が問題だったのかを理解する助けになります。

例: メソッド引数の検証

public static int safeDivide(int a, int b) {
    if (b == 0) {
        throw new IllegalArgumentException("除数は 0 にできません");
    }
    return a / b;
}

使用例:

public static void main(String[] args) {
    try {
        int result = safeDivide(10, 0);
        System.out.println("結果: " + result);
    } catch (IllegalArgumentException e) {
        System.out.println("エラー: " + e.getMessage());
    }
}

結果:

エラー: 除数は 0 にできません

throw を使うのはどんなとき?

  • メソッド引数の検証(たとえば null や不正なデータが来た場合)。
  • オブジェクトの状態チェック(たとえば口座残高が 0 ユーロなのに出金しようとした場合)。
  • catch の内部(たとえば追加情報を付けて例外を「投げ直す」場合)。

4. try-catch-finally と throw の組み合わせ

これらの構文を組み合わせて使うことがあります。たとえば、ある例外を捕捉したうえで、より情報量の多い独自の例外をスローする、といったケースです。

public static int parseAndDivide(String text, int divisor) {
    try {
        int number = Integer.parseInt(text);
        if (divisor == 0) {
            throw new IllegalArgumentException("除数は 0 にできません");
        }
        return number / divisor;
    } catch (NumberFormatException e) {
        throw new IllegalArgumentException("文字列 '" + text + "' は数値ではありません");
    } finally {
        System.out.println("文字列の処理を試みました: " + text);
    }
}

使用例:

try {
    int result = parseAndDivide("42a", 2);
    System.out.println("結果: " + result);
} catch (IllegalArgumentException e) {
    System.out.println("エラー: " + e.getMessage());
}

結果:

文字列の処理を試みました: 42a
エラー: 文字列 '42a' は数値ではありません

重要な注意点: return と finally

try ブロック内に return があっても、finally は必ず実行されます。

public static int getValue() {
    try {
        return 10;
    } finally {
        System.out.println("finally は必ず実行されます!");
    }
}

getValue() の呼び出しは次を出力します:

finally は必ず実行されます!

5. finally と throw のよくあるミス

エラー No.1: finally を使わずにリソースを閉じ忘れる。
とてもよくある問題です。ファイルを開いて閉じ忘れると、リソースリークにつながります。常に finally(または後述する try-with-resources)を使用しましょう。

エラー No.2: 例外をスローしたのに捕捉しない。
throw で例外をスローしても、どこでも捕捉されない(try-catch がない)場合、プログラムは異常終了します。あなたのスローした例外を「誰が捕まえるのか」を常に考えましょう。

エラー No.3: finally 内での return。
誤って returnfinally 内に書くと、先にあった returnthrow を上書きしてしまいます。これは非常に発見しづらいバグにつながります。絶対に避けましょう。

public int tricky() {
    try {
        return 1;
    } finally {
        return 2; // 危険: 1 ではなく 2 が返る!
    }
}

結果: try1 があっても、返るのは 2 です。

エラー No.4: 例外情報の喪失。
ある例外を捕捉してから新しい例外をスローする際に、元の例外(e)を保持しないと、スタックトレースを失い、デバッグが難しくなります。次のように書くのが良いでしょう:

catch (NumberFormatException e) {
    throw new IllegalArgumentException("変換エラー", e);
}
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION