【JavaGold勉強記#22】try-with-resources編

JavaGold資格取得に向けた勉強記第22回は「try-with-resources」についてです。

Java 7で導入されたtry-with-resources文は、リソースの自動クローズをサポートする便利な機能です。

この機能を使用することで、ファイル、データベース接続、ネットワークソケットなどのリソースを簡単に管理できます。

この記事では、try-with-resources文の基本的な使い方と注意点について解説します。

try-with-resourcesとは

try-with-resourcesは、Java SE 7で導入された機能で、プログラムの中で扱うリソースを自動的に閉じるためのものです。一般的にプログラムで扱うリソースとは、プログラムからアクセスするデータを指しますが、Javaではデータだけでなく、アクセスするためのインスタンスなどもリソースとして扱います。

リソースは、使う時に開き使い終わったら閉じなければいけません。しかし、「リソースを使い終わったら閉じる」のは任意であって、必須ではないため、プログラマーが閉じ忘れることがあります。リソースを閉じるコードを書かなくてもコンパイルエラーにはならず、正常に処理できてしまうため、テストでも見つかりにくいです。

このようなミスを防ぐために、自動的にリソースを閉じるための構文として導入されたのが、try-with-resourcesです。

try-with-resourcesの基本的な使用方法

AutoCloseableまたはClosableを実装

try-with-resourcesを使用するクラスは、AutoCloseableまたはCloseableインターフェースを実装する必要があります。どちらもcloseメソッドをもっており、それぞれスローする例外が異なります

以下で、それぞれのインタフェースの実装例を紹介します。

AutoCloseable

AutoCloseable インターフェースは、Java 7で導入されました。単一のメソッド close() を持っており、Exception型の例外をスローします。

public class MyResource implements AutoCloseable {
    @Override
    public void close() throws Exception {
        System.out.println("Resource closed");
    }
}

Closable

close() メソッドのシグニチャは同じものの、スローする例外クラスが IOExceptionとなっています。

public class MyCloseableResource implements Closeable {
    @Override
    public void close() throws IOException {
        System.out.println("Resource closed");
    }
}

try-with-resourcesの使用

try-with-resourcesでは、tryキーワードの後ろのカッコ内で宣言したリソースが自動的に閉じる対象になります。

try ( 自動的に閉じるリソースの宣言 ){
  リソースを使った処理
} 
try (ResourceType resource = new ResourceType()) {
    resource.performAction();
} catch (Exception e) {
    // 例外処理
}

try-with-resourcesで自動的に閉じる対象とするリソースは、tryに続くカッコだけでなく、次のようにtryブロックの前に宣言を記述することも可能です。

ResourceType resource = new ResourceType()
try ( resource ) {
    resource.performAction();
} catch (Exception e) {
    // 例外処理
}

try-with-resources使用時の注意点

複数のリソースを対象とする場合

なお、複数のリソースを対象とする場合は、セミコロン「;」で区切って列挙します。複数対象とした場合、宣言されたリソースが閉じる順番は、宣言した時の逆の順番です。

public class TestUsing {
    public static void main(String[] args) throws Exception {
        A a = new A();
        try (a;
                B b = new B();
                C c = new C()) {
            // do something
        }
    }
}

catchブロック、finallyブロックを使用する場合

try-with-resourcesを使う場合でも、catchブロックやfinallyブロックをつけ、例外処理を実行することができます。例外発生の有無に関わらず、リソースはtryブロックを抜けるタイミングで必ず実行されます。

そのため、tryブロック内で例外が発生しなければ、以下の順番で実行されます。

  1. リソースのクローズ
  2. finallyブロック

例外が発生すれば、以下の順番で実行されます。

  1. リソースのクローズ
  2. catchブロック
  3. finallyブロック

抑制された例外

Javaのtry-with-resources文やマルチキャッチ機能を使用すると、複数の例外が発生した場合、例外が抑制される可能性があります。抑制された例外とは、tryブロック内で発生した例外と、try-with-resourcesまたはマルチキャッチで発生した例外の両方を処理するために、try-with-resourcesまたはマルチキャッチが抑制(suppressed)する例外のことです。

class MyResource implements AutoCloseable {

    @Override
    public void close() throws Exception {
        System.out.println("Closing the resource");
    }

    public void doSomething() throws Exception {
        System.out.println("Doing something with the resource");
        throw new Exception("An exception from doSomething");
    }
}

public class Example {
    public static void main(String[] args) {
        try (MyResource resource = new MyResource()) {
            resource.doSomething();
        } catch (Exception e) {
            System.out.println("Caught exception: " + e.getMessage());
            for (Throwable suppressed : e.getSuppressed()) {
                System.out.println("Suppressed: " + suppressed.getMessage());
            }
        }
    }
}

この例では、doSomething メソッド内で例外がスローされ、try-with-resources ブロック内でリソースがクローズされると同時に、クローズ中にも例外が発生します。このとき、try-with-resources ブロックで発生した例外は e に捕捉され、close メソッド内で発生した例外は e の抑制された例外として扱われます。

抑制された例外の取得

Throwable クラスには、抑制された例外を取得するための getSuppressed メソッドがあります。上記の例では、e.getSuppressed() で抑制された例外の配列を取得し、それをループで処理しています。

for (Throwable suppressed : e.getSuppressed()) {
    System.out.println("Suppressed: " + suppressed.getMessage());
}

最後に

try-with-resources文は、Javaプログラムでリソースを効率的に管理するための重要な機能です。

リソースのクローズ処理を簡潔に記述し、リソースリークを回避するために、try-with-resources文を積極的に活用しましょう。