Java | 基礎文法:finally

Java Java
スポンサーリンク

finally の全体像

finally は「例外が起きても起きなくても、最後に必ず実行したい後始末」を書くための節です。try で本処理、必要なら catch で失敗時の対応を行い、最後に finally でリソース解放や状態の復元を実行します。例外が発生しても、return していても、基本的には finally が走るため、後始末の信頼性を高められます。


実行タイミングと基本フロー

try-catch-finally の流れを掴む

try の中で処理を行い、対応する例外が発生したら catch に移ります。その後、finally は必ず呼ばれます。例外が発生しなかった場合も finally は呼ばれます。これにより、開いたファイルを閉じる、ロックを解放する、設定を元に戻すといった「必須の後処理」を確実に行えます。

try {
    System.out.println("処理開始");
    int x = Integer.parseInt("123");
    System.out.println("OK: " + x);
} catch (NumberFormatException e) {
    System.err.println("変換失敗: " + e.getMessage());
} finally {
    System.out.println("必ず通る後始末");
}
Java

リソース解放と try-with-resources との関係(重要ポイントの深掘り)

finally でのクローズ

I/O やソケットなどの外部リソースは、例外の有無に関わらず確実に閉じる必要があります。古典的には finally で null チェックのうえ close を呼びます。

java.io.BufferedReader br = null;
try {
    br = new java.io.BufferedReader(new java.io.FileReader("input.txt"));
    System.out.println(br.readLine());
} catch (java.io.IOException e) {
    System.err.println("読み込み失敗: " + e.getMessage());
} finally {
    if (br != null) {
        try { br.close(); } catch (java.io.IOException ignored) {}
    }
}
Java

try-with-resources が推奨される理由

AutoCloseable を実装したリソースは、try-with-resources を使うことで、finally に書かなくても自動でクローズされます。クローズ時の例外は「サプレスト例外」として本体の例外にぶら下がるため、原因特定が容易です。I/O・DB・ネットワークでは、まず try-with-resources を選び、finally は「その他の後始末(フラグ復元など)」へ使い分けるのが最適です。

import java.nio.file.*;
import java.io.*;

try (var br = Files.newBufferedReader(Path.of("input.txt"))) {
    System.out.println(br.readLine());
} catch (IOException e) {
    e.printStackTrace();           // 本体の例外
    for (Throwable t : e.getSuppressed()) {
        System.err.println("suppressed: " + t); // close時の例外
    }
}
Java

finally の落とし穴(重要ポイントの深掘り)

return と例外を「上書き」してしまう

finally の中で return を書くと、try/catch 内の return や例外よりも優先され、結果を上書きします。これは意図せぬバグにつながるため、finally 内での return は避けます。

static int bad() {
    try {
        return 1;
    } finally {
        return 2; // 1 を上書きしてしまう:悪手
    }
}
Java

例外を投げ直すのも慎重に。finally で新しい例外を投げると、元の例外を失って原因追跡が難しくなります。ログだけ出す、フラグ復元だけ行うなど、後始末に限定しましょう。

例外の握り潰し

finally 内で例外が起きたときに無条件で握り潰すと、根本原因が見えなくなります。必要ならログを残す、サプレストとして付ける、といった形で情報を保ちます。ただし、リソースの close 失敗は多くの場面で致命的ではないため、運用方針に従って扱いを決めます。

状態の復元漏れ

一時的に変更した設定(タイムアウト、ログレベル、スレッドローカルなど)は、finally で必ず元に戻します。戻し忘れは後続処理を破壊します。try の直前に「元の値を保持」、finally で「復元」を定型化すると安全です。


使いどころの整理とスコープ設計

「失敗しうる最小の塊」を try、後処理を finally に

try の範囲を広げすぎると、どこで失敗したか不明瞭になります。リソース取得〜使用〜解放の最小単位に絞って try を置き、finally で後処理を明確にするのが読みやすさ・保守性の両面で有利です。finally で使う変数は、try の外で宣言しておくと、例外発生時にも参照できます。

boolean oldFlag = Config.isDebug();
try {
    Config.setDebug(true);
    doWork();
} finally {
    Config.setDebug(oldFlag); // 必ず元へ戻す
}
Java

例題で身につける

例 1: ロックの取得と解放

import java.util.concurrent.locks.ReentrantLock;

var lock = new ReentrantLock();
lock.lock();
try {
    // 共有資源の操作
    System.out.println("critical section");
} finally {
    lock.unlock(); // 例外があっても必ず解放
}
Java

例 2: 設定の一時変更と復元

class Config {
    private static boolean debug = false;
    static boolean isDebug() { return debug; }
    static void setDebug(boolean v) { debug = v; }
}

boolean old = Config.isDebug();
try {
    Config.setDebug(true);
    // デバッグモードでの処理
} finally {
    Config.setDebug(old); // 元に戻す
}
Java

例 3: finally の return が結果を上書きする悪例と回避

static int good() {
    try {
        return 1;
    } finally {
        // 後始末のみ。return は書かない
        System.out.println("cleanup");
    }
}
Java

仕上げのアドバイス(重要部分のまとめ)

finally は「最後に必ず実行される後始末」のために使う節です。リソース解放は try-with-resourcesを優先し、finally は設定復元やロック解放など「必須のクリーンアップ」に集中させると安全です。finally 内での return や新たな例外送出は、元の結果や原因を上書きするため避けます。try のスコープを最小化し、外で使う状態は外で保持、finally で確実に復元する——この癖が、例外時でも壊れないコードをつくります。

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