finally ブロック
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 = 0 で ArithmeticException が飛んでも、計算が成功しても、戻り値の末尾には必ず ,end がついているという挙動を組み立てることが目的です。
文法 ─ try catch finally の並び
基本の文法は次のとおりです。
Java
try {
// 例外が起きるかもしれない処理
} catch (ArithmeticException e) {
// 例外をキャッチして復旧する処理
} finally {
// 例外が出ても出なくても必ず実行される後始末
}finally は単独で try に付けることもできます (catch なしで try-finally だけ書く)。catch が複数あっても OK で、その場合も finally はいちばん最後に 1 つだけ書きます。順序のルールは try → catch(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(); が走った後でも、メソッドが外に値を返す 前に finally の reader.close() が呼ばれます。例外が出てメソッドを抜ける場合も同じです。これが「リソース解放の伝統的な場所」と呼ばれる理由です。
finally が動く順番をフローで追う
言葉だけだと分かりにくいので、図で正常時と例外時の経路を比べてみましょう。
大事なのは「正常ルート」「例外ルート」のどちらを通っても、最後に同じ finally を通ってから外に出る、という構造です。これを意識すると、finally がただの後付け装飾ではなく、「処理の合流地点」であることが見えてきます。
tryの中でreturnを書いた場合、Java は 戻り値を一度内部の一時変数に確保してからfinallyを実行し、その後で値を返す と覚えてください。なのでfinallyでString変数を書き換えても、すでに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) を呼ぶと、try で message = "result=5" まで進み、finally で ",end" が足されて "result=5,end" が返ります。一方 demo(10, 0) だと ArithmeticException が飛び、catch で message = "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していないのに、finallyでreturnしてしまうと、本来呼び出し元に伝わるはずだった例外が消えてしまいます。デバッグ時に「エラーがどこかで握りつぶされている」と気づくのが非常に難しくなるので、finallyの中でreturnするのは原則禁止と覚えておきますfinallyで参照する変数を初期化していない —tryブロックの中だけで宣言した変数は、finallyのスコープ外です。readerのようなリソース変数はtryの 外側 で宣言し、nullで初期化しておき、finallyでif (reader != null) reader.close();のようにチェックしてから使うのが定石です- リソース解放を
tryの最後に書いて、例外時に呼ばれない — 「reader.close()をtryブロックの最後の行に書けば良い」と思いがちですが、try中で例外が出るとそこから先は実行されません。close()が呼ばれず、ファイルハンドルやコネクションがリークします。リソース解放はfinally、もしくはtry-with-resourcesに必ず移す のが鉄則です
「
finally内returnを絶対書かない」「リソースはfinallyかtry-with-resourcesに書く」の 2 つを徹底するだけで、finallyまわりのバグはほぼ防げます。
もう 1 つ補足すると、finally 内で 新しい例外を投げる のもバグの温床です。try で起きた本来の例外が新しい例外で上書きされてしまい、原因の特定が一気に難しくなります。どうしても finally 内で例外になり得る処理を書く場合は、try-catch で囲んでログだけ残す、といった配慮が必要です。
今回の課題に向けて
ここまで読めば、今回の課題のイメージはだいたい掴めたはずです。やることはシンプルで、次のとおりです。
int aとint bを受け取るtrackedDivideメソッドをSolutionクラスに作るtryの中でa / bを計算し、"result=" + 商の形に組み立てるArithmeticExceptionをcatchしたら"error"をセットするfinallyで末尾に",end"を必ず付け足す- 完成した文字列を
returnする
例として、trackedDivide(10, 2) は "result=5,end"、trackedDivide(10, 0) は "error,end"、trackedDivide(9, 3) は "result=3,end" を返すようにします。整数除算なので "result=3" のように小数点はつかない、という点も忘れずに。
やってみよう
それでは実装してみましょう。ヒントは次のとおりです。
String message;のように、trycatchfinallyのすべてから書き換えできる変数をtryの外側 で宣言しておく- 結果は
message = "result=" + (a / b);のように、intと文字列の連結を使うと素直に組める finallyの中ではmessage = message + ",end";で末尾を追加する。returnはfinallyの 外 に 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 つができることを確認しましょう。
- finally の要点を自分の言葉で説明できる
- このレッスンの最小コード (または操作手順) を見ずに書ける
- 練習問題やクイズで間違えた箇所を読み直して理解した
理解度チェック (30 秒)
Q. finally とは何か、1 文で説明してください。
A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。
関連レッスン
要件
tryブロックの中でa / bを計算し、正常時は"result=" + 商の文字列を組み立てることArithmeticExceptionをcatchして、例外時は"error"の文字列を組み立てること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"