finallyブロック:例外があってもなくても動く処理

docs

 

前回は、Javaの例外処理について、try-catchを使ってプログラムがエラーで止まるのを防ぐ方法を見てきましたね。

プログラムがエラーを起こしても、try-catchを使えば「こんなエラーが起きたら、こう対処してね!」と指示できます。でも、実は例外処理にはもう一つ、とっても大事な仲間がいるんです。それが今回ご紹介する**finallyブロック**!

finallyブロックは、名前の通り「最終的に」実行されるブロックで、tryブロックの処理がうまくいっても、例外が発生してcatchブロックの処理が行われても、とにかく必ず実行されるという特徴があります。

「え、なんでそんなのが必要なの?」と思うかもしれませんね。でも、これがプログラムの安定性やリソースの管理において、めちゃくちゃ重要なんです。

なぜfinallyブロックが必要なの?

例えば、ファイルを開いて書き込みをするプログラムを考えてみましょう。

  1. ファイルを開く
  2. ファイルに書き込む
  3. ファイルを閉じる

この流れで、もし「ファイルに書き込む」途中でエラーが起きたらどうなるでしょう? try-catchでエラーはキャッチできますが、開いたままのファイルは閉じられず、開きっぱなしになってしまう可能性があります。これが続くと、他のプログラムがそのファイルを使えなくなったり、最悪の場合、システム全体に悪影響が出たりすることも。

こんな時こそfinallyブロックの出番です!「ファイルを閉じる」処理をfinallyブロックの中に書いておけば、ファイルに書き込む途中で例外が発生しても、しなくても、確実にファイルが閉じられることを保証できます。

finallyブロックの基本的な形

finallyブロックは、try-catchブロックのすぐ後ろに書きます。

Java

try {
    // 例外が発生する可能性がある処理
} catch (例外の種類 変数名) {
    // 例外が発生した場合の処理
} finally {
    // 例外の有無にかかわらず、必ず実行される処理
    // 後片付けやリソースの解放など、重要な処理を書くことが多い
}

catchブロックは例外の種類によって複数書くこともできますが、finallyブロックは1つのtryブロックに対して1つだけです。

実際に見てみよう!finallyブロックの動き

いくつか例を見て、finallyブロックがどう動くのかをしっかり理解しましょう。

例1:例外が発生しない場合

まずは、何も問題なく処理が進むパターンです。

Java

public class FinallyExample1 {
    public static void main(String[] args) {
        try {
            System.out.println("--- tryブロック開始 ---");
            int result = 10 / 2; // 例外は発生しない
            System.out.println("計算結果: " + result);
            System.out.println("--- tryブロック終了 ---");
        } catch (ArithmeticException e) {
            System.out.println("catchブロック: 0で割ろうとしました!");
        } finally {
            System.out.println("finallyブロック: 必ず実行されます!");
        }
        System.out.println("プログラム終了。");
    }
}

実行結果:

--- tryブロック開始 ---
計算結果: 5
--- tryブロック終了 ---
finallyブロック: 必ず実行されます!
プログラム終了。

見ての通り、tryブロックの処理が全て実行された後、finallyブロックが実行され、最後にプログラムが終了していますね。catchブロックはスキップされています。

paizaで実行した結果

例2:例外が発生する場合(catchブロックで処理)

次に、わざと例外を発生させて、catchブロックが動くパターンです。

Java

public class FinallyExample2 {
    public static void main(String[] args) {
        try {
            System.out.println("--- tryブロック開始 ---");
            int result = 10 / 0; // 0で割ろうとしてArithmeticExceptionが発生!
            System.out.println("計算結果: " + result); // ここは実行されない
            System.out.println("--- tryブロック終了 ---");
        } catch (ArithmeticException e) {
            System.out.println("catchブロック: 0で割ろうとしました! 例外メッセージ: " + e.getMessage());
        } finally {
            System.out.println("finallyブロック: 例外が発生しても必ず実行されます!");
        }
        System.out.println("プログラム終了。");
    }
}

実行結果:

--- tryブロック開始 ---
catchブロック: 0で割ろうとしました! 例外メッセージ: / by zero
finallyブロック: 例外が発生しても必ず実行されます!
プログラム終了。

tryブロックで例外が発生した瞬間に処理は中断され、すぐにcatchブロックにジャンプしています。そして、catchブロックの処理が終わった後、期待通りfinallyブロックが実行されていますね。

paizaで実行した結果

例3:returnがあってもfinallyは動く!

さらに重要なポイント!メソッド内でreturn文を使って処理を途中で終了させようとしても、finallyブロックは必ず実行されます。

Java

public class FinallyExample3 {
    public static void main(String[] args) {
        System.out.println("メソッド呼び出し結果: " + demonstrateFinally());
        System.out.println("プログラム終了。");
    }

    public static String demonstrateFinally() {
        try {
            System.out.println("--- demonstrateFinally(): tryブロック開始 ---");
            if (true) {
                return "tryブロックからreturn!"; // ここでメソッドを終了しようとする
            }
            System.out.println("この行は実行されません。");
        } catch (Exception e) {
            System.out.println("demonstrateFinally(): catchブロック!");
        } finally {
            System.out.println("demonstrateFinally(): finallyブロック! returnがあっても必ず実行される!");
        }
        return "demonstrateFinally(): 通常終了。"; // この行は実行されない
    }
}

実行結果:

--- demonstrateFinally(): tryブロック開始 ---
demonstrateFinally(): finallyブロック! returnがあっても必ず実行される!
メソッド呼び出し結果: tryブロックからreturn!
プログラム終了。

tryブロック内のreturn文が実行され、メソッドが終了しようとしていますが、その前にしっかりfinallyブロックが実行されているのが分かりますね。これは、ファイルのクローズなど、どんな状況でも必ず実行したい「後片付け」の処理を確実に動かすために非常に重要な特性です。

paizaで実行した結果

どんな時にfinallyを使うの?

finallyブロックは、主に以下のような「後片付け」や「リソースの解放」が必要な場面で活躍します。

  • ファイルのクローズ: 開いたファイルを確実に閉じる。
    • FileWriterBufferedReaderなどを使った後に。
  • データベース接続のクローズ: データベースへの接続を確実に切断する。
    • ConnectionStatementResultSetなどを使った後に。
  • ネットワーク接続のクローズ: 開いたネットワーク接続を確実に閉じる。
  • メモリの解放: 大量のメモリを使った処理の後、不要になったメモリを解放する(Javaの場合はGCがあるので意識することは少ないですが、特定のリソースでは必要)。
  • ロックの解放: マルチスレッド処理で確保したロックを確実に解放する。

これらの処理は、例外が発生しようがしまいが、プログラムが正常に動作し続けるために絶対に必要なものです。

try-with-resources文という便利な仲間

ファイルやデータベースのクローズ処理は、finallyブロックで書くのが基本ですが、Java 7からは、もっとスマートにリソースを解放できる**try-with-resources文**という便利な機能が追加されました。

例えば、ファイルを読み込むコードはこんな感じになります。

Java

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class TryWithResourcesExample {
    public static void main(String[] args) {
        // try()の中に、クローズする必要のあるリソースを宣言する
        try (BufferedReader reader = new BufferedReader(new FileReader("example.txt"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            System.err.println("ファイル読み込み中にエラーが発生しました: " + e.getMessage());
        }
    }
}

このtry-with-resources文を使うと、tryの丸かっこ()の中で宣言したリソース(この場合はBufferedReader)が、tryブロックの処理が終わった時に自動的に閉じられます。つまり、finallyブロックで明示的にクローズ処理を書かなくてもよくなるんです!これはすごく便利ですよね。

ただし、このtry-with-resourcesが使えるのは、AutoCloseableインターフェースを実装しているクラスのリソースだけです。ほとんどのリソースクラスはこれに該当するので、知っておくとコードがスッキリしますよ。

paizaで実行した結果

まとめ

今回は、Javaの例外処理における重要な要素、finallyブロックについて学習しました。

  • finallyブロックは、tryブロックやcatchブロックの例外の有無にかかわらず、必ず実行される処理を書く場所です。
  • 主に、ファイルやデータベース接続などの**リソースの解放(後片付け)**に利用されます。
  • return文でメソッドを終了しようとしても、finallyブロックは実行されます。
  • Java 7以降では、特定の条件下で**try-with-resources文**を使うことで、finallyブロックでのリソース解放処理を省略できる場合があります。

finallyブロックを適切に使うことで、プログラムの安定性を高め、リソースの無駄遣いを防ぐことができます。これは、プロとして信頼される堅牢なプログラムを書く上で欠かせない知識です。

これからも一緒にJavaの理解を深めていきましょう!

コメント

タイトルとURLをコピーしました