finally ブロック

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

finally ブロック とは

Java の try 文で必ず実行される finally ブロックの役割をリソース解放や後始末の観点から学び、整数除算の結果を ,end 付きで返すメソッドを実装してみよう。

finally ブロックは「絶対に通る出口」

try 文と catch 句の組み合わせは、例外をキャッチして処理を続行するための仕組みでした。ところが現場のコードを書いていると、もう一段強い保証がほしくなる場面が出てきます。たとえば、ファイル を開いた後の close()データベース接続 の解放、ログの「処理終了」記録などです。これらは 正常終了でも例外発生でも、必ず一度だけ実行されてほしい 処理です。

そんな「絶対に通る出口」を作るのが finally ブロックです。try の中で何が起きても、return が走っても、未 catch の例外で抜けても、finally のコードは必ず実行されてからメソッドの外に出ます。

Java の言語仕様上、JVM そのものが System.exit(0) や強制終了で落ちる場合だけは finally も走りません。それ以外の通常の Java プログラムでは、finally は「必ず通る」と思って良い、というのが本章の出発点です。

本章では、整数除算 (a / b) の結果を返すメソッドに finally で「末尾に ,end を追加する」という後始末を入れてみます。b = 0ArithmeticException が飛んでも、計算が成功しても、戻り値の末尾には必ず ,end がついているという挙動を組み立てることが目的です。

文法 ─ try catch finally の並び

基本の文法は次のとおりです。

Java

try { // 例外が起きるかもしれない処理 } catch (ArithmeticException e) { // 例外をキャッチして復旧する処理 } finally { // 例外が出ても出なくても必ず実行される後始末 }

finally は単独で try に付けることもできます (catch なしで try-finally だけ書く)。catch が複数あっても OK で、その場合も finally はいちばん最後に 1 つだけ書きます。順序のルールは trycatch(0 個以上) → finally(任意の 1 個) です。

Java

import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; public class FileSample { public static String readFirstLine(String path) throws IOException { BufferedReader reader = null; try { reader = new BufferedReader(new FileReader(path)); return reader.readLine(); } finally { if (reader != null) { reader.close(); } } } }

この例では、return reader.readLine(); が走った後でも、メソッドが外に値を返す 前に finallyreader.close() が呼ばれます。例外が出てメソッドを抜ける場合も同じです。これが「リソース解放の伝統的な場所」と呼ばれる理由です。

finally が動く順番をフローで追う

言葉だけだと分かりにくいので、図で正常時と例外時の経路を比べてみましょう。

diagram (will load when visible)

大事なのは「正常ルート」「例外ルート」のどちらを通っても、最後に同じ finally を通ってから外に出る、という構造です。これを意識すると、finally がただの後付け装飾ではなく、「処理の合流地点」であることが見えてきます。

try の中で return を書いた場合、Java は 戻り値を一度内部の一時変数に確保してから finally を実行し、その後で値を返す と覚えてください。なので finallyString 変数を書き換えても、すでに return 済みの値そのものは変わりません (後述の落とし穴と関係します)。

コード例 1 ─ シンプルに finally を使う

小さい例で動きを確かめます。

Java

public class FinallyDemo { public static String demo(int a, int b) { String message; try { int q = a / b; message = "result=" + q; } catch (ArithmeticException e) { message = "error"; } finally { message = message + ",end"; } return message; } }

demo(10, 2) を呼ぶと、trymessage = "result=5" まで進み、finally",end" が足されて "result=5,end" が返ります。一方 demo(10, 0) だと ArithmeticException が飛び、catchmessage = "error"finally",end" を追加して "error,end" が返ります。どちらの経路でも末尾の ,end が確実に付く ことが finally の威力です。

コード例 2 ─ try-with-resources との対比

リソース解放の用途では、Java 7 以降では try-with-resources 構文の方が安全で簡潔です。次のように書きます。

Java

import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; public class FileSampleModern { public static String readFirstLine(String path) throws IOException { try (BufferedReader reader = new BufferedReader(new FileReader(path))) { return reader.readLine(); } } }

try (...) のかっこの中で宣言したリソースは、AutoCloseable インターフェイスを実装していれば、try ブロックを抜けるときに自動で close() してくれます。finally を書かなくても、Java が裏で「finally 相当の close 呼び出し」を組み立ててくれるイメージです。

新規に ファイル ソケット JDBC などのリソースを扱うときは、まず try-with-resources を検討するのが現代の Java の流儀です。finally での手動 close は古いコードや、AutoCloseable ではないリソースに限定して使います。

それでも finally 自体は不要にはなりません。後片付けが「close() 一発」では済まないとき (ログを出す、ステータスを書き戻す、メモリ上のフラグを更新する、再試行回数をカウントするなど) には、今でも finally が一番素直な選択肢です。

finally でよくある間違い

初学者が引っかかりやすい落とし穴を 3 つ紹介します。

  • finally の中で return を書いて例外を握りつぶすtry から飛んできた例外を catch していないのに、finallyreturn してしまうと、本来呼び出し元に伝わるはずだった例外が消えてしまいます。デバッグ時に「エラーがどこかで握りつぶされている」と気づくのが非常に難しくなるので、finally の中で return するのは原則禁止と覚えておきます
  • finally で参照する変数を初期化していないtry ブロックの中だけで宣言した変数は、finally のスコープ外です。reader のようなリソース変数は try外側 で宣言し、null で初期化しておき、finallyif (reader != null) reader.close(); のようにチェックしてから使うのが定石です
  • リソース解放を try の最後に書いて、例外時に呼ばれない — 「reader.close()try ブロックの最後の行に書けば良い」と思いがちですが、try 中で例外が出るとそこから先は実行されません。close() が呼ばれず、ファイルハンドルやコネクションがリークします。リソース解放は finally、もしくは try-with-resources に必ず移す のが鉄則です

finallyreturn を絶対書かない」「リソースは finallytry-with-resources に書く」の 2 つを徹底するだけで、finally まわりのバグはほぼ防げます。

もう 1 つ補足すると、finally 内で 新しい例外を投げる のもバグの温床です。try で起きた本来の例外が新しい例外で上書きされてしまい、原因の特定が一気に難しくなります。どうしても finally 内で例外になり得る処理を書く場合は、try-catch で囲んでログだけ残す、といった配慮が必要です。

今回の課題に向けて

ここまで読めば、今回の課題のイメージはだいたい掴めたはずです。やることはシンプルで、次のとおりです。

  1. int aint b を受け取る trackedDivide メソッドを Solution クラスに作る
  2. try の中で a / b を計算し、"result=" + 商 の形に組み立てる
  3. ArithmeticExceptioncatch したら "error" をセットする
  4. finally で末尾に ",end" を必ず付け足す
  5. 完成した文字列を return する

例として、trackedDivide(10, 2)"result=5,end"trackedDivide(10, 0)"error,end"trackedDivide(9, 3)"result=3,end" を返すようにします。整数除算なので "result=3" のように小数点はつかない、という点も忘れずに。

やってみよう

それでは実装してみましょう。ヒントは次のとおりです。

  • String message; のように、try catch finally のすべてから書き換えできる変数を try の外側 で宣言しておく
  • 結果は message = "result=" + (a / b); のように、int と文字列の連結を使うと素直に組める
  • finally の中では message = message + ",end"; で末尾を追加する。returnfinally に 1 行だけ書く
  • b = 0 の場合は a / b を評価した瞬間に ArithmeticException が飛ぶ。if (b == 0) で事前判定せずに、あえて try-catch で受ける形にする (本章の主題が finally なので)

実装が終わったら、trackedDivide(8, 4) trackedDivide(7, 0) trackedDivide(-6, 2) など、自分で入力を変えて挙動を確かめてみてください。マイナスの整数除算は Java では 0 方向への切り捨て (-6 / 4 == -1) になる、という細かい仕様にも気付けると、より深く理解できます。

次のレッスンでは、ここで触れた try-with-resources を本格的に扱い、AutoCloseable なリソースを安全に閉じる現代的な書き方を学んでいきます。まずは finally の「必ず通る」という性質をしっかり手に馴染ませてから先に進みましょう。

よくある質問

Q. 中級の内容は実務でどれくらい使いますか?

A. Collection(List/Map/Set)、Stream、例外処理、ジェネリクスは毎日のように登場します。Date/Time API、Files、try-with-resources も実務で頻出するため、本コースの内容は実プロジェクトでそのまま役立ちます。

Q. Stream と for ループはどっちで書くべき?

A. 可読性で選んでください。filter + map + collect が綺麗にハマるなら Stream、副作用や複雑な分岐が多いなら for ループの方が読みやすいです。一律にどちらを使うべきという正解はなく、チームのコーディング規約に合わせるのが現実的です。

Q. 次のステップでは何を学ぶべきですか?

A. 中級の基礎が固まったら、Spring Boot で Web API を作る、JUnit でテストを書く、Maven/Gradle でビルドを管理する、といった実プロジェクトのスキルに進むと効果的です。OSS のコードを読む経験も大きく成長を促します。

次のレッスン

次は 例外処理 まとめクイズ で、例外処理 まとめクイズ を学びます。

事前確認 — 進む前に次の 3 つができることを確認しましょう。

  1. finally の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  3. 練習問題やクイズで間違えた箇所を読み直して理解した

理解度チェック (30 秒)

Q. finally とは何か、1 文で説明してください。

この章のポイント

A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。

関連レッスン

要件

  1. try ブロックの中で a / b を計算し、正常時は "result=" + 商 の文字列を組み立てること
  2. ArithmeticExceptioncatch して、例外時は "error" の文字列を組み立てること
  3. finally ブロック (または finally と同等の最終処理) を用いて、正常時・例外時のいずれでも戻り値の末尾に必ず ",end" を付け加えること

入出力例

test-cases.txt

trackedDivide(10, 2)"result=5,end" trackedDivide(10, 0)"error,end" trackedDivide(9, 3)"result=3,end" trackedDivide(20, 4)"result=5,end" trackedDivide(7, 0)"error,end"

ヒント

main.java
main.java
学習モード

メモ

finally ブロック

⌘S で保存