Java超|finallyで安全な後処理を行う

どんな展開になっても、最後の後片付けは finally が守ってくれる。Javaの finally は、例外が起きても起きなくても実行したい処理を書くための大切な締めくくり役です。

プログラムでは、処理がうまく進むこともあれば、途中で例外が起きることもあります。

try-catch を使えば、例外が起きたときに catch で受け止めて、分かりやすいメッセージを表示したり、別の処理へ進めたりできます。

しかし、実際のプログラムでは、それだけでは足りない場面があります。

たとえば、次のような処理です。

最後に必要な処理なぜ必要か
ファイルを閉じる開いたままだと次の処理に影響することがある
ネットワーク接続を終了する接続しっぱなしを防ぐため
データベース接続を閉じる資源を使い続けないようにするため
使用中の装置や資源を解放する無駄な利用や不整合を防ぐため
最後の確認メッセージを出す処理の終わりを分かりやすくするため

このような処理は、例外が起きた場合だけ必要になるわけではありません。
例外が起きても、起きなくても、最後に必ず行いたい処理です。

そこで使うのが finally です。

finally は、try-catch と組み合わせて使い、最後に必ず実行したい処理を書くためのブロックです。
例外が起きた場合でも、起きなかった場合でも、finally の中の処理は実行されます。

ドラゴンボール風にたとえると、危険な修行や任務のあとに、必ず重力室を停止し、スカウターの記録を閉じ、カプセル装置を安全状態に戻すようなものです。

悟空の修行が無事に終わっても、途中でトラブルが起きても、最後には重力装置を止める必要があります。
ベジータが修行中に異常値を出しても、装置をつけっぱなしにしておくわけにはいきません。

Javaの finally も同じです。

try で処理を試す。
例外が起きたら catch で受け止める。
そして、どんな流れになっても finally で最後の後片付けをする。

この考え方を身につけると、より安全で丁寧なプログラムを書けるようになります。

finallyとは何か

finally は、try-catch と組み合わせて使うブロックです。

役割はとてもシンプルです。

最後に必ず実行したい処理を書く場所です。

基本の形は次のようになります。

try {
    例外が起きるかもしれない処理
}
catch(例外のクラス 変数名) {
    例外が起きたときの処理
}
finally {
    最後に必ず行う処理
}

try、catch、finally の役割を整理すると、次のようになります。

ブロック役割ドラゴンボール風のイメージ
try例外が起きるかもしれない処理を書く危険を含む修行や任務を実行する
catch例外が起きたときに受け止めるピッコロやブルマの支援がトラブルを受け止める
finally最後に必ず実行する処理を書く重力室の停止、記録装置の終了、装備の片づけを行う

finally は、例外処理そのものを書く場所というより、処理の締めくくりを書く場所です。

たとえば、戦士本部の重力室で、悟空たちの戦闘力を記録する処理を考えます。

修行記録が成功しても、途中で名簿の範囲外を指定してしまっても、最後には記録装置を止める必要があります。

この「どんな結果でも最後に行う処理」を書くのが finally です。

try-catchだけでは足りない場面

try-catch を使えば、例外が起きたときの対応はできます。

しかし、後片付け処理を書く場所を間違えると、必要な処理が実行されない場合があります。

たとえば、後片付け処理を try の最後に書いた場合です。

try {
    危険な処理
    後片付け処理
}
catch(例外のクラス e) {
    例外時の処理
}

この場合、危険な処理で例外が起きると、try の残りは実行されません。
つまり、try の最後に書いた後片付け処理まで到達できないことがあります。

一方、後片付け処理を catch の中だけに書いた場合は、例外が起きなかったときに実行されません。

try {
    危険な処理
}
catch(例外のクラス e) {
    例外時の処理
    後片付け処理
}

これでは、正常に処理が終わったときに後片付けが行われません。

そこで finally を使います。

後片付けを書く場所問題点
try の最後try の途中で例外が起きると実行されないことがある
catch の中だけ例外が起きなかった場合に実行されない
finally例外の有無に関係なく実行される

ドラゴンボール風に言えば、修行が成功したときだけ重力室を停止する、またはトラブルが起きたときだけ停止する、というのは不十分です。

修行が成功しても、失敗しても、最後に重力室や記録装置を安全に止める必要があります。

そのための場所が finally です。

finallyの基本の流れ

finally は、try-catch の最後に置かれます。

流れは次のようになります。

状況trycatchfinally
例外が起きない最後まで実行される実行されない実行される
例外が起きて catch が一致する途中で中断される実行される実行される

つまり finally は、正常終了でも例外発生でも実行されます。

ここが finally の一番大切なポイントです。

try が最後まで進んでも finally は動きます。
try の途中で例外が起き、catch に移動しても finally は動きます。

ドラゴンボール風に言えば、悟空の修行が成功しても、ベジータの重力修行中に警報が鳴っても、最後には重力室の安全装置を切る、ということです。

図:finallyは最後に必ず実行される

この図が示していること

この図は、例外が起きない場合と、例外が起きる場合の両方で finally が実行されることを表しています。

例外が起きない場合は、try の処理が最後まで進み、その後 finally が実行されます。

例外が起きる場合は、try の途中で処理が中断され、catch に移動します。
その後、finally が実行されます。

流れfinally の動き
try が正常に終わるfinally が実行される
try の途中で例外が起きるcatch のあとに finally が実行される

この図から分かることは、finally が「正常時だけ」「例外時だけ」の処理ではなく、どちらの場合でも実行される締めくくりの処理だということです。

finally の動きを確認する

ここでは、配列の範囲外アクセスを使って、finally の動きを確認します。

5人分の戦闘力を記録できる配列を用意します。
しかし、10番目に値を書き込もうとします。

配列の範囲をこえるため、ArrayIndexOutOfBoundsException が発生します。

その例外を catch で受け止めたあと、finally が実行される流れを見ていきます。

ファイル名:Sample3.java

class Sample3
{
    public static void main(String[] args)
    {
        try {
            int[] power;
            power = new int[5];

            System.out.println("戦士本部の修行名簿の10番目に戦闘力を記録します。");

            power[10] = 9000;
            System.out.println("10番目への記録が完了しました。");
        }
        catch(ArrayIndexOutOfBoundsException e) {
            System.out.println("記録できる範囲をこえています。");
        }
        finally {
            System.out.println("重力修行室の記録装置を必ず停止します。");
        }

        System.out.println("修行データの確認を終えました。");
    }
}

プログラムの全体像

このプログラムには、try、catch、finally の3つがあります。

try {
    int[] power;
    power = new int[5];

    System.out.println("戦士本部の修行名簿の10番目に戦闘力を記録します。");

    power[10] = 9000;
    System.out.println("10番目への記録が完了しました。");
}
catch(ArrayIndexOutOfBoundsException e) {
    System.out.println("記録できる範囲をこえています。");
}
finally {
    System.out.println("重力修行室の記録装置を必ず停止します。");
}

それぞれの役割は次の通りです。

部分内容
try配列を作り、10番目に戦闘力を記録しようとする
catch配列の範囲外アクセスを受け止める
finally重力修行室の記録装置を必ず停止する
try-catch-finally の後ろ修行データの確認終了を表示する

ドラゴンボール風に言えば、try は修行名簿への記録作業です。
catch は名簿の範囲外を指定したときの支援役の対応です。
finally は、どんな結果でも最後に記録装置を止める後片付けです。

配列の範囲外アクセスが起きる

このプログラムでは、次のように配列を作成しています。

int[] power;
power = new int[5];

new int[5] によって、5個分の int 型データを入れる場所が作られます。

Javaの配列の添字は 0 から始まります。

配列の要素数使える添字
50, 1, 2, 3, 4

つまり、power 配列で使えるのは power[0] から power[4] までです。

しかし、次の行では power[10] に値を入れようとしています。

power[10] = 9000;

これは範囲外アクセスです。

そのため、ArrayIndexOutOfBoundsException が発生します。

処理結果
power[0] から power[4]使える
power[10]範囲外なので例外が発生する

例外が発生すると、try の後続処理は中断されます。

そのため、次の文は実行されません。

System.out.println("10番目への記録が完了しました。");

記録に失敗しているため、成功メッセージには進まないわけです。

catchで例外を受け止める

配列の範囲外アクセスによって ArrayIndexOutOfBoundsException が発生すると、対応する catch が動きます。

catch(ArrayIndexOutOfBoundsException e) {
    System.out.println("記録できる範囲をこえています。");
}

catch の丸かっこの中には、受け止めたい例外の種類を書きます。

部分意味
ArrayIndexOutOfBoundsException配列の範囲外アクセスを表す例外
e受け取った例外を入れる変数名

今回発生する例外は ArrayIndexOutOfBoundsException です。
catch に書いている種類と一致するため、catch の中の処理が実行されます。

ドラゴンボール風に言えば、修行名簿の存在しない欄に記録しようとした警告を、ブルマの解析システムが受け止めて、

「記録できる範囲をこえています。」

と知らせてくれるイメージです。

finallyで最後の処理を実行する

catch の処理が終わると、finally が実行されます。

finally {
    System.out.println("重力修行室の記録装置を必ず停止します。");
}

この finally は、例外が起きたかどうかに関係なく実行されます。

今回のプログラムでは、例外が起きています。
それでも finally は実行されます。

状況finally
例外が起きる実行される
例外が起きない実行される

このサンプルでは、finally の中で「重力修行室の記録装置を必ず停止します。」と表示しています。

これは学習用の分かりやすい例ですが、実際のプログラムでは、ファイルを閉じる、接続を終了する、資源を解放する、といった処理を書く場面があります。

ドラゴンボール風にたとえると、修行が成功しても失敗しても、最後に重力装置を止め、記録装置を閉じ、カプセル道具を保管棚へ戻すような処理です。

実行結果

このプログラムを実行すると、次のようになります。

戦士本部の修行名簿の10番目に戦闘力を記録します。
記録できる範囲をこえています。
重力修行室の記録装置を必ず停止します。
修行データの確認を終えました。

この流れを表にすると、次のようになります。

表示どこで実行されたか
戦士本部の修行名簿の10番目に戦闘力を記録します。try
記録できる範囲をこえています。catch
重力修行室の記録装置を必ず停止します。finally
修行データの確認を終えました。try-catch-finally の後ろ

ここで注目したいのは、finally のメッセージが表示されていることです。

try の中で例外が起きています。
それでも finally の処理は飛ばされません。

さらに、catch と finally の処理が終わったあと、try-catch-finally の後ろにある処理も実行されています。

Sample3.javaの処理の流れ

このプログラムの流れを順番に整理すると、次のようになります。

順番処理実際の動き
1try に入る修行名簿への記録処理を始める
2配列を作るpower = new int[5]; が実行される
3登録開始メッセージを表示10番目に戦闘力を記録すると表示
4power[10] に代入しようとする範囲外なので例外が発生
5try の残りを中断記録完了メッセージは実行されない
6catch に移動範囲外アクセスを受け止める
7catch の処理を実行記録できる範囲をこえています。
8finally を実行重力修行室の記録装置を必ず停止します。
9後続処理へ進む修行データの確認を終えました。

try-catch-finally の流れで大切なのは、finally が catch のあとに実行されていることです。

例外が起きた場合は、try から catch へ移動し、その後 finally へ進みます。

図:Sample3.javaでfinallyが動く流れ

この図が示していること

この図は、Sample3.java の中で例外が発生し、catch と finally が順番に実行される流れを表しています。

try の中で power[10] = 9000; が実行されると、配列の範囲外アクセスが起きます。

そのため、try の中にある記録完了メッセージは実行されません。

その後、catch が例外を受け止め、finally で記録装置の停止処理を行います。

図のポイント内容
power[10]例外が発生する場所
記録完了メッセージ例外発生後なので実行されない
catch範囲外アクセスを受け止める
finally最後に必ず実行される
後続処理finally のあとに進む

この図から分かることは、finally は try の最後にある処理ではなく、try-catch の流れの最後に用意された特別な後処理ブロックだということです。

finallyはどんなときに実行されるのか

finally は、例外が起きた場合だけに実行されるものではありません。

例外が起きても、起きなくても実行されます。

もう一度、流れを整理します。

状況trycatchfinally
例外が起きない最後まで実行される実行されない実行される
例外が起きて catch が一致する途中で中断される実行される実行される

この性質があるため、finally には後片付け処理を書くのに向いています。

ドラゴンボール風にたとえると、修行が成功しても、途中で名簿の記録ミスが起きても、最後には重力修行室の記録装置を停止するということです。

任務の結果最後に行うこと
修行記録が成功した記録装置を停止する
範囲外アクセスで失敗した記録装置を停止する
catch で対応した記録装置を停止する

finally は、処理の成功や失敗に左右されず、最後に必要な作業を実行するための場所です。

try・catch・finallyの役割の違い

try、catch、finally は、並んで書くことが多いため、役割を混同しやすいです。

しかし、それぞれの担当ははっきり違います。

ブロック何を書くか目的
try例外が起きるかもしれない処理まず処理を試す
catch例外が起きたときの対応問題を受け止める
finally最後に必ず行う処理後片付けや終了処理を行う

try は、通常の処理を進める場所です。

catch は、例外が起きたときの対応を書く場所です。

finally は、成功したか失敗したかに関係なく、最後に行う処理を書く場所です。

ドラゴンボール風に整理すると、次のようになります。

Javaドラゴンボール風の役割
try修行名簿に戦闘力を記録する処理
catch範囲外の記録ミスを受け止める支援役
finally最後に重力装置や記録装置を停止する記録係

このように役割を分けると、try-catch-finally の流れがかなり分かりやすくなります。

なぜfinallyが必要なのか

finally が必要になる理由は、重要な後処理を実行し忘れないためです。

たとえば、ファイルを開いて作業するプログラムを考えます。

ファイルを開いたあと、処理が正常に終われば、最後にファイルを閉じる必要があります。

しかし、途中で例外が起きた場合でも、ファイルは閉じなければなりません。

もしファイルを閉じる処理を try の最後にだけ書いていたら、try の途中で例外が起きたときに実行されない可能性があります。

もし catch の中にだけ書いていたら、例外が起きなかった場合に実行されません。

だから finally が必要です。

書き方問題点
try の最後に後処理を書くtry の途中で例外が起きると実行されないことがある
catch の中だけに後処理を書く例外が起きない場合に実行されない
finally に後処理を書く例外の有無に関係なく実行される

プログラムでは、成功したときの流れだけでなく、失敗したときも後始末できることが重要です。

finally は、そのための仕組みです。

finallyに書くべき処理

finally に書くのは、最後に必ず行う必要がある処理です。

代表的なものは次の通りです。

処理の種類finally に書く理由
ファイルを閉じるファイルを開いたままにしないため
通信を終了する接続しっぱなしを防ぐため
データベース接続を閉じる資源を解放するため
装置や資源を解放する使い終わったものを戻すため
最後の確認メッセージを出す処理の終わりを明確にするため

今回のサンプルでは、学習用に次のメッセージを finally に書いています。

System.out.println("重力修行室の記録装置を必ず停止します。");

これは、実務でいう「使い終わった資源を閉じる」「最後の後片付けを行う」という処理のイメージです。

ドラゴンボール風にたとえると、重力修行室の記録装置を止める、通信装置を切る、カプセル道具を保管棚へ戻すような処理です。

図:finallyに書く後処理のイメージ

この図が示していること

この図は、finally に書くべき処理のイメージを表しています。

finally は、例外対応そのものを書く場所ではなく、最後に必ず行いたい後処理を書く場所です。

finally に向いている処理内容
ファイルを閉じる開いた巻物を閉じる
通信を終了する通信装置の接続を切る
接続を閉じる使い終わった端末を停止する
装置を停止する重力修行室の記録装置を止める

この図から分かることは、finally がプログラムの後始末を安全に行うための場所だということです。

catchがあってもfinallyが大切な理由

catch があれば、例外が起きたときに対応できます。

しかし、catch は「例外が起きたとき」に動くブロックです。

一方、finally は「最後に必ず」動くブロックです。

この違いが重要です。

ブロック実行される条件
catch対応する例外が起きたとき
finally例外の有無に関係なく実行される

もし後片付けを catch にだけ書いてしまうと、例外が起きなかった場合には実行されません。

だから、後片付けのように必ず必要な処理は finally に書くのが自然です。

ドラゴンボール風にたとえると、ピッコロの支援はトラブルが起きたときに対応します。
しかし、ブルマの設備停止チェックは、トラブルが起きても起きなくても必要です。

catch はトラブル対応。
finally は最後の締め作業。

このように分けて考えると、役割がすっきりします。

catchが見つからない場合とfinally

finally は、catch がある場合だけ大切なのではありません。

例外処理では、現在のメソッド内に対応する catch が見つからない場合、呼び出し元へ例外が伝わっていくことがあります。

そのような場合でも、そのメソッドを離れる前に必ず行いたい処理があります。

たとえば、ドラゴンボール風に言えば、任務の最終判断は戦士本部へ引き継ぐとしても、その場の重力修行室の装置だけは止めてから離れたい、という場面です。

finally は、そのような「この場所を離れる前に必ず行う処理」を書くためにも役立ちます。

状況finally の意味
catch で例外を受け止めるcatch のあとに後処理を行う
catch が見つからず呼び出し元へ進むその前に必要な後処理を行う
例外が起きない通常処理のあとに後処理を行う

finally は、処理の流れがどう進んでも、必要な締めくくりを行うための重要なしくみです。

finallyで押さえておきたいポイント

finally を学ぶときは、次の点を押さえておくと理解しやすいです。

ポイント内容
finally の役割最後に必ず行う処理を書く
実行される条件例外が起きても起きなくても実行される
try の中で例外が起きた場合catch のあとに finally が実行される
try が正常に終わった場合catch は動かず finally が実行される
書く内容後片付け、終了処理、接続解除、資源解放など
使う意味重要な処理の実行漏れを防ぐ

finally は派手な機能ではありません。

しかし、実際のプログラムではとても大切です。

プログラムでは、成功したときだけでなく、失敗したときにもきちんと後始末できることが重要です。

ドラゴンボール風に言えば、強い技を出すことだけが任務ではありません。
修行のあとに重力装置を止め、記録を閉じ、修行室を整えるところまでが大切です。

Javaの finally も同じです。

例外が起きても、起きなくても、最後に必要な処理をきちんと実行する。
そのための締めくくり役が finally です。