1. finally ブロックの概要
ファイル・ネットワーク接続・データベースなどのリソースを扱うときは、処理中にエラーが起きても必ずそれらをクローズまたは解放できることが重要です。Java にはそのための専用ブロック finally があります。
finally はどのように動作するか?
finally ブロックは、try-catch-finally 構文の一部です。finally 内のコードは常に実行されます(記述されていれば)。例外の有無に関係なく実行されます。たとえ try 内で return した場合や例外がスローされた場合でも、finally は実行されます(コンピュータの電源断や System.exit(0) による強制終了などの特殊なケースを除く)。
構文:
try {
// 例外が発生しうるコード
} catch (ExceptionType e) {
// エラー処理
} finally {
// このコードは必ず実行される
}
例
try {
System.out.println("処理開始");
int result = 10 / 0; // ここでエラーが発生する
System.out.println("結果: " + result);
} catch (ArithmeticException e) {
System.out.println("エラー: ゼロで除算");
} finally {
System.out.println("このコードは必ず実行されます");
}
実行結果:
処理開始
エラー: ゼロで除算
このコードは必ず実行されます
何が起きているのか?
- try で 2 つの数を割ろうとしてエラーになります。
- 割り算でエラーが発生した場合は catch がそれを処理します。
- しかし! いずれの場合も finally は実行され、コンソールにメッセージを出力します。
2. catch なしの finally
構成は次の3通りがあります:
- 完全形: try-catch-finally
- finally なし: try-catch
- catch なし: try-finally
3 つ目は、エラーの捕捉と処理を上位のメソッドに任せる場合に使います。ただし finally ブロックは、特定のコードを必ず実行させるために必要です:
- ファイル、ネットワーク接続、データベースのクローズ。
- あらゆるリソースの解放(たとえばロック)。
- ロギング(操作終了の記録)。
例:
try {
System.out.println("数を割ります");
int result = 10 / 0; // エラー!
System.out.println("結果: " + result);
} finally {
System.out.println("finally ブロックが実行されました");
}
結果:
数を割ります
finally ブロックが実行されました
Exception in thread "main" java.lang.ArithmeticException: / by zero
finally が実行されないのはいつ?
ほぼ常に実行されます。例外は次のような場合です:
- System.exit(0) を使ってプログラムを強制終了した。
- finally が実行されるスレッドを強制的に停止した。
- コンピュータの電源が切れた。
3. throw 演算子: 自分で例外をスローする方法
Java は(ゼロ除算や配列の範囲外アクセスなど)自動で例外を「投げる」ことがあります。しかし、自分で「これはエラーだ。続行できない」と明示したい場面もあります。そのために Java には throw 演算子があります。
たとえ話: お店で消費期限切れの商品を見つけたら苦情を「投げ」ますよね。コードでも同様に、何かおかしければ例外をスローします。
throw の構文
throw new ExceptionType("エラーメッセージ");
ExceptionType は Throwable を継承する任意のクラスです(通常は Exception または RuntimeException)。括弧内のメッセージは、何が問題だったのかを理解する助けになります。
例: メソッド引数の検証
public static int safeDivide(int a, int b) {
if (b == 0) {
throw new IllegalArgumentException("除数は 0 にできません");
}
return a / b;
}
使用例:
public static void main(String[] args) {
try {
int result = safeDivide(10, 0);
System.out.println("結果: " + result);
} catch (IllegalArgumentException e) {
System.out.println("エラー: " + e.getMessage());
}
}
結果:
エラー: 除数は 0 にできません
throw を使うのはどんなとき?
- メソッド引数の検証(たとえば null や不正なデータが来た場合)。
- オブジェクトの状態チェック(たとえば口座残高が 0 ユーロなのに出金しようとした場合)。
- catch の内部(たとえば追加情報を付けて例外を「投げ直す」場合)。
4. try-catch-finally と throw の組み合わせ
これらの構文を組み合わせて使うことがあります。たとえば、ある例外を捕捉したうえで、より情報量の多い独自の例外をスローする、といったケースです。
public static int parseAndDivide(String text, int divisor) {
try {
int number = Integer.parseInt(text);
if (divisor == 0) {
throw new IllegalArgumentException("除数は 0 にできません");
}
return number / divisor;
} catch (NumberFormatException e) {
throw new IllegalArgumentException("文字列 '" + text + "' は数値ではありません");
} finally {
System.out.println("文字列の処理を試みました: " + text);
}
}
使用例:
try {
int result = parseAndDivide("42a", 2);
System.out.println("結果: " + result);
} catch (IllegalArgumentException e) {
System.out.println("エラー: " + e.getMessage());
}
結果:
文字列の処理を試みました: 42a
エラー: 文字列 '42a' は数値ではありません
重要な注意点: return と finally
try ブロック内に return があっても、finally は必ず実行されます。
public static int getValue() {
try {
return 10;
} finally {
System.out.println("finally は必ず実行されます!");
}
}
getValue() の呼び出しは次を出力します:
finally は必ず実行されます!
5. finally と throw のよくあるミス
エラー No.1: finally を使わずにリソースを閉じ忘れる。
とてもよくある問題です。ファイルを開いて閉じ忘れると、リソースリークにつながります。常に finally(または後述する try-with-resources)を使用しましょう。
エラー No.2: 例外をスローしたのに捕捉しない。
throw で例外をスローしても、どこでも捕捉されない(try-catch がない)場合、プログラムは異常終了します。あなたのスローした例外を「誰が捕まえるのか」を常に考えましょう。
エラー No.3: finally 内での return。
誤って return を finally 内に書くと、先にあった return や throw を上書きしてしまいます。これは非常に発見しづらいバグにつながります。絶対に避けましょう。
public int tricky() {
try {
return 1;
} finally {
return 2; // 危険: 1 ではなく 2 が返る!
}
}
結果: try に 1 があっても、返るのは 2 です。
エラー No.4: 例外情報の喪失。
ある例外を捕捉してから新しい例外をスローする際に、元の例外(e)を保持しないと、スタックトレースを失い、デバッグが難しくなります。次のように書くのが良いでしょう:
catch (NumberFormatException e) {
throw new IllegalArgumentException("変換エラー", e);
}
GO TO FULL VERSION