【Java】try-with-resourcesのNullや例外の話

try-with-resourcesがJava7で登場してcloseの処理を自分で書くことがなくなり便利になりました。

しかし今までのclose処理とことなるため、try句のリソースがnullだったときの挙動やtry句とclose処理の両方で例外が発生した時の挙動が忘れがちになっているので、try-with-resourcesのアレコレのちょっとした疑問をまとめていきます。

[スポンサーリンク]

try-with-resourcesのおさらい

try-with-resourcesの登場前はfinally句でcloseする処理を必ず書く必要がありました。

private void read(String path) throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(path));
    try {
        System.out.println(br.readLine());
    } finally {
      if (br != null) {
          br.close();
      }
    }
}

それがtry-with-resourcesの登場によって自分でcloseの処理を書かなくてよくなりました。
try句のリソースのclose処理は勝手にやってくれるということですね。
便利になりました。

private void read2(String path) throws IOException {
    try (BufferedReader br = new BufferedReader(new FileReader(path))) {
        System.out.println(br.readLine());
    }
}

try-with-resourcesに複数リソースを指定

もちろん複数リソースを指定することもできます。

private void read3(String path) throws IOException {
    try (BufferedReader br = new BufferedReader(new FileReader(path));
         BufferedReader br2 = new BufferedReader(new FileReader(path))) {
        System.out.println(br.readLine() + br2.readLine());
    }
}

try句のリソースがNullの場合

try-with-resourcesの登場前はリソースのclose処理前にNullチェックをしていましたが、try-with-resourcesのtry句のリソースがNullの場合はどうなのでしょうか?

このようにリソースがNullの場合でも自分でNullチェックを入れることは不要です。
リソースのclose処理でNullPointerExceptionが発生するということはありません。

private void read5(String path) throws IOException {
    try (BufferedReader br = null) {
        System.out.println("test");
    }
}

try句内とclose処理で同時に例外が発生した場合

try句内とclose処理の両方で例外が発生したケース。
このケースはちょっと考えてしまいますね。
では、その前にtry-with-resourcesより前のケースではどうなるのか確認してみましょう。

この場合は、 finallyブロック(br.close)からスローされた例外のみをスローします。

private void read(String path) throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(path));
    try {
        System.out.println(br.readLine());
    } finally {
      if (br != null) {
          br.close();
      }
    }
}

続いてtry-with-resourcesのケースではどうなるのでしょうか?

もしtry句内とbrのclose処理の両方で例外が発生した場合は、try句内から発生した例外のみをスローします。
close処理で発生した例外は握りつぶされるということになります。

private void read2(String path) throws IOException {
    try (BufferedReader br = new BufferedReader(new FileReader(path))) {
        System.out.println(br.readLine());
    }
}

ちなみに参考にしたのはコチラの公式サイトです。

ただし、この例では、readLine と close メソッドの両方が例外をスローした場合、メソッド readFirstLineFromFileWithFinallyBlock は finally ブロックからスローされた例外をスローします。try ブロックからスローされた例外は抑制されます。これに対し、readFirstLineFromFile の例では、try ブロックと try-with-resources 文の両方から例外がスローされた場合、メソッド readFirstLineFromFile は try ブロックからスローされた例外をスローします。try-with-resources ブロックからスローされた例外は抑制されます。Java SE 7 以降では、抑制された例外を取得できます。詳細は、「抑制された例外」を参照してください。

try-with-resourcesとそれ以前では挙動がことなっていますね。

ではtry-with-resourcesでリソースのclose処理で発生した例外を取得するにはどうすればよいのでしょうか?

こちら Throwable.getSuppressed を使えばclose処理で発生した例外も取得できます!

コチラが参考文献です。

これらの抑制された例外は、try ブロックからスローされた例外から Throwable.getSuppressed メソッドを呼び出すことで取得できます。

このような感じで、try句とclose処理の両方で発生した例外を取得します。

private void read4(String path) throws IOException {
    try (BufferedReader br = new BufferedReader(new FileReader(path))) {
        System.out.println(br.readLine());
    } catch (Exception e) {
        System.out.println(e.getMessage());
        for (Throwable exception : e.getSuppressed()) {
            System.out.println(exception.getMessage());
        }
    }
}

さいごに

try-with-resourcesとそれ以前の書き方だと例外の扱われ方を意識しておいた方がよいですね。

忘れそうなのでその時はこのメモを見るようにしようと思います。

それでは!