Code Fix

上級

finally の return が結果を上書きする問題

finallyでreturnを書いてはいけない理由と、try-with-resourcesによる安全なリソース解放を扱います。

なぜエラーが出ないのか

出力: 2
(エラーなし)
javacは何も報告しません。文法として正しいためです
出力: 2
実際の挙動 — 期待した結果と食い違っている箇所
見つけ方
エラーが出ないので、出力を目で確かめるしかありません。この種の誤りが最も発見が遅れます

このエラーが出る典型パターン

パターン1

 1  public class Main {
 2      static int f() {
 3          try { return 1; }
 4          finally             { return 2; }
                     ^
 5      }
 6      public static void main(String[] args) {
 7          System.out.println(f());
 8      }
 9  }
出力: 2期待: 1 (コンパイルは通っています)

finally内のreturnはtry内のreturnを上書きします。finallyでreturnを書かないのが鉄則です。

直し方: finally を catch (Exception e) にします。

この問題を解いてみる →

広告
広告スロット(未設定)

パターン2

 1  import java.io.*;
 2  
 3  public class Main {
 4      public static void main(String[] args) throws IOException {
 5          try (String         r = new BufferedReader(new StringReader("a"))) {
                        ^
 6              System.out.println(r.readLine());
 7          }
 8      }
 9  }
Main.java:5: error: incompatible types: BufferedReader cannot be converted to String

try-with-resourcesの変数はAutoCloseableを実装した型で宣言します。ブロックを抜けると自動でcloseされます。

直し方: String を BufferedReader にします。

この問題を解いてみる →

パターン3

 1  public class Main {
 2      public static void main(String[] args) {
 3          try {
 4              System.out.println("try");
 5          }         {
                 ^
 6              System.out.println("always");
 7          }
 8      }
 9  }
Main.java:5: error: 'try' without 'catch', 'finally' or resource declarations

tryは単独では書けません。catchかfinally、またはリソース宣言のいずれかが必要です。

直し方: (空) を finally にします。

この問題を解いてみる →

パターン4

 1  public class Main {
 2      static void f() {
 3          try {
 4              throw new RuntimeException("try-error");
 5          } finally {
 6              throw new RuntimeException("finally-error");
                                      ^
 7          }
 8      }
 9      public static void main(String[] args) {
10          f();
11      }
12  }
Exception in thread "main" java.lang.RuntimeException: finally-errorexited with code 1(try-errorではなくfinally-errorが伝播する)

finallyの中で例外を投げると、tryで発生した元の例外は握りつぶされ、finallyの例外だけが伝わります。

直し方: throw new RuntimeException("finally-error"); を System.out.println("cleanup"); にします。

この問題を解いてみる →

パターン5

 1  public class Main {
 2      static void risky() throws java.io.IOException, InterruptedException {
 3          throw new java.io.IOException("x");
 4      }
 5      public static void main(String[] args) {
 6          try {
 7              risky();
 8          } catch (java.io.IOException ,  InterruptedException e) {
                                          ^
 9              System.out.println("caught");
10          }
11      }
12  }
Main.java:8: error: <identifier> expected

複数の型を1つのcatchでまとめて捕まえるマルチキャッチは | 区切りです。カンマでは書けません。

直し方: , を | にします。

この問題を解いてみる →

よくある誤解

finallyは必ず実行されると言われますが、System.exit() を呼んだ場合は実行されません。

実務での勘所

finallyで例外や戻り値を上書きしてしまう問題は、リソース解放の文脈で特に起きやすいため、Java 7以降はtry-with-resourcesという専用の構文が用意されました。この構文ではclose()の中で新たに例外が発生しても、元の例外を上書きして消してしまうのではなく、addSuppressedという仕組みで元の例外に「抑制された例外」として記録され、両方の情報が残ります。対象がCloseableを実装しているなら、自分でfinallyに書くよりtry-with-resourcesに任せるほうがこの種の情報消失を避けられます。

演習をはじめる

関連するエラー

広告
広告スロット(未設定)