【Java】try-with-resources構文について

スポンサーリンク

Java7から追加されたtry-with-resources構文についてです。Java8についても関係がありましたので使い方からまとめておきます。

try-with-resourcesとは

JavaSE6まではファイルやデータベースへのアクセスをするとリソースを解放するためにclose処理を記述していました。毎回close処理を書くのも面倒ですし、忘れるとメモリリークの原因になったりもします。そこでJavaSE7から追加されたtry-with-resources文を使うとこれらの問題が解決できるのです。

try-with-resources文は、1つ以上のリソースを宣言するtry文です。リソースは、プログラムでの使用が終わったら閉じられなければいけないオブジェクトです。try-with-resources文は、文の終わりで各リソースが確実に閉じられるようにします。
参照 :  try-with-resources文

サンプルで確認

まずはJavaSE6までの例です。finallyブロックでclose()を呼び出していますが、nullチェックを行ってからclose()の例外をcatchするために再度tryブロックを記述したりと少し面倒ですね。

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class SampleTryWithResources {
    public static void main(String[] args) {
        BufferedReader br = null;
        try {
            br = new BufferedReader(new FileReader("test.txt"));
            String line = null;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (br != null)
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
        }
    }
}

そこでJavaSE7から導入されたtry-with-resources文で記述すると次のようになります。ポイントはtryのすぐ後にクローズの対象となるリソースの生成処理を記述していることです。この方法によってfinallyブロックでclose()を呼び出さなくても自動でcloseをしてくれるようになります。リソースが複数ある場合はセミコロンで区切ってください。

        try (BufferedReader br = new BufferedReader(new FileReader("test.txt"))) {
            String line = null;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

AutoCloseableインターフェース

try-with-resources文を使用できるのは、java.lang.AutoCloseablejava.io.Closeableインターフェースの実装クラスである必要があります。CloseableはAutoCloseableのサブインターフェースです。このインターフェースにはclose()メソッドのみ宣言されており、java.io や java.sql パッケージなどのリソース関連クラスは、これらのインターフェースを実装しているため、暗黙的に解放することができるようになりました。

独自クラスでもAutoCloseableインターフェースを実装した場合、自動でクローズされる対象になるので試してみます。

class SampleFileReader implements AutoCloseable {
    @Override
    public void close() throws IOException {
        System.out.println("SampleFileReader Close.");
    }
}

class SampleBufferedReader implements AutoCloseable {
    @Override
    public void close() throws IOException {
        System.out.println("SampleBufferedReader Close.");
    }
}

...

try (SampleBufferedReader br = new SampleBufferedReader();
     SampleFileReader fr = new SampleFileReader()) {
    throw new IOException();
    System.out.println("--start--");
} catch (IOException e) {
    System.out.println("--IOException--");
} finally {
    System.out.println("--finally--");
}

実行結果は次に通りです。tryブロック内で例外を発生させたところ、catchされる前にclose()メソッドが呼び出されているのがわかります。また、リソースの取得と逆順でcloseされています。finallyブロックを記述した場合も通常通り最後に実行されるようです。

--start--
SampleFileReader Close.
SampleBufferedReader Close.
--IOException--
--finally--

抑制された例外

try-with-resources文の流れは一通り理解できたと思いますが、close()メソッドで例外が発生した場合はどうなるのでしょう。これはJavaSE7の「抑制された例外」という概念で、tryブロックと、try-with-resources文(closeメソッド)の両方から例外が発生した場合は、try-with-resources文からの例外は抑制され、tryブロックの例外がスローされるようになっています。

そのため抑制された例外を取得するにはJavaSE7からThrowableクラスに追加されたgetSuppressed()メソッドを使用します。

class SampleFileReader implements AutoCloseable {
    @Override
    public void close() throws IOException {
        System.out.println("SampleFileReader Close.");
    }
}

class SampleBufferedReader implements AutoCloseable {
    @Override
    public void close() throws IOException {
        System.out.println("SampleBufferedReader Close.");
        throw new IOException("closeメソッド例外");
    }
}

...

try (SampleBufferedReader br4 = new SampleBufferedReader();
     SampleFileReader br5 = new SampleFileReader()) {
    System.out.println("--start--");
    throw new IOException("tryブロック例外");
} catch (IOException e) {
    System.out.println("--IOException--" + e.getMessage());
    Throwable[] ary = e.getSuppressed();
    Arrays.stream(ary).forEach(ex -> System.out.println("--IOException--" + ex.getMessage()));
} finally {
    System.out.println("--finally--");
}

実行結果は次の通りです。抑制されていたcloseメソッドの例外も取得できました。

--start--
SampleFileReader Close.
SampleBufferedReader Close.
--IOException--tryブロック例外
--IOException--closeメソッド例外
--finally--

JavaSE8におけるtry-with-resources

JavaSE8から追加された Stream API は AutoCloseable を実装しているのでtry-with-resources文で利用可能です。

例えば Stream API を使ってワンライナーで書いてしまいたい場合でも、これだとリソースの解放漏れが発生してしまいます。

Files.lines(Paths.get("test.txt"), StandardCharsets.UTF_8).forEach(System.out::println);

このような場合はtry-with-resources文を使うようにしましょう。

try (Stream<String> stream = Files.lines(Paths.get("test.txt"), StandardCharsets.UTF_8)) {
    stream.forEach(System.out::println);
} catch (IOException e1) {
    e1.printStackTrace();
}

参考記事

 Amazon S3でtry-with-resources文を使う | Developers.IO

 StreamはAutoCloseableであると認識していないとアレな件 - mike-neckのブログ