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) {}
}
}
Javatry-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時の例外
}
}
Javafinally の落とし穴(重要ポイントの深掘り)
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 で確実に復元する——この癖が、例外時でも壊れないコードをつくります。
