1. 導入:なぜfinallyブロックが重要なのか
Java開発において、リソース(ファイル、データベース接続、ネットワークソケットなど)の解放は避けて通れない課題です。もし例外が発生したり、予期せぬ制御フローが走ったりしてリソース解放が漏れると、メモリリークやシステムダウンを引き起こします。finallyブロックは、どのような状況下であっても「必ず実行されるコード」を記述するための強力な仕組みです。特に近年のJavaで導入された複雑な制御フローとの組み合わせにおいても、その動作を正確に理解しておくことは、堅牢なシステムを構築する上で不可欠です。
2. 基礎知識:finallyブロックの基本動作
try-catch-finally構造において、finallyブロックは「tryまたはcatchブロックの実行が完了した後」に必ず実行されます。Javaの仕様では、たとえtryブロック内でreturn文が実行されたとしても、finallyブロックが先に処理されてからメソッドが終了します。近年のJavaで導入されたswitch式やyield、sealed class等を用いた複雑な分岐処理においても、この「実行保証」のルールは変わりません。
3. 実装と解決策
制御フロー(if-elseやswitch式)がどれほど複雑になっても、リソースのクローズ処理などはfinallyブロックに集約させるのが定石です。ただし、注意が必要なのは「finallyブロック内で例外が発生した場合」や「System.exit()が呼び出された場合」です。これらを除き、JVMが正常に稼働している限り、finallyは必ず実行されます。
4. サンプルプログラム
以下のコードは、最新のJava構文(switch式)を用いた制御フローと、finallyの実行順序を確認する実用的な例です。
public class FinallyGuaranteeDemo {
public static void main(String[] args) {
System.out.println("戻り値: " + executeTask(2));
}
public static String executeTask(int type) {
try {
System.out.println("--- 処理開始 ---");
// switch式を用いた制御フロー
String result = switch (type) {
case 1 -> "Type 1";
case 2 -> {
// ここでreturnや例外が発生してもfinallyは実行される
if (type == 2) throw new RuntimeException("意図的な例外発生");
yield "Type 2";
}
default -> "Unknown";
};
return result;
} catch (Exception e) {
System.out.println("例外をキャッチしました: " + e.getMessage());
return "Error Handling";
} finally {
// 制御フローがどこへ分岐しようと、ここは必ず実行される
System.out.println("--- finally: リソースのクローズ処理をここに記述 ---");
}
}
}
5. 応用・注意点:現場で陥りやすい罠
現場のシニアエンジニアとして特に注意喚起したい点は以下の2つです。
・finally内での例外発生:
finallyブロック内で例外が発生すると、try-catchブロックで発生していた元の例外が隠蔽(抑制)されてしまうことがあります。finally内でのクローズ処理を行う際は、必ず個別にtry-catchを入れ、例外が外に漏れないように設計してください。
・try-with-resourcesの検討:
Java 7以降、AutoCloseableインターフェースを実装したリソースであれば、try-with-resources構文を使用する方が推奨されます。コードが簡潔になり、finallyブロックの記述漏れによるバグを劇的に減らすことができます。finallyはあくまで「リソース解放以外の、必ず実行したい後処理」のために使う、という使い分けを意識しましょう。

コメント