Menu
Coddy logo textTech

Try-With-Resources パターン

CoddyのJavaジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 62/87。

ファイルストリームやデータベース接続などのリソースを扱う場合、終了時には必ずそれらを閉じなければなりません。そうしないと、メモリリークのリスクがあります。伝統的なアプローチでは finally ブロックを使用しますが、冗長でエラーが発生しやすいです。

Javaの try-with-resources 文は、ブロックが終了したときにリソースを自動的にクローズします。AutoCloseable インターフェースを実装するすべてのクラスは、この構文で使用できます。

// 従来のアプローチ - 冗長でリスクがある
BufferedReader reader = null;
try {
    reader = new BufferedReader(new FileReader("file.txt"));
    String line = reader.readLine();
} finally {
    if (reader != null) {
        reader.close();  // ここでも例外が発生する可能性があります!
    }
}

// Try-with-resources - クリーンで安全
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
    String line = reader.readLine();
}  // ここで自動的にクローズされます

セミコロンで区切ることで、複数のリソースを管理できます。これらは宣言とは逆の順序でクローズされます:

try (FileReader fr = new FileReader("input.txt");
     BufferedReader br = new BufferedReader(fr)) {
    // 両方のリソースを使用します
}  // brが最初にクローズされ、次にfrがクローズされます

独自の auto-closeable クラスを作成するには、AutoCloseable インターフェースとその唯一の close() メソッドを実装します:

public class DatabaseConnection implements AutoCloseable {
    public DatabaseConnection() {
        System.out.println("Connection opened");
    }
    
    @Override
    public void close() {
        System.out.println("Connection closed");
    }
}

このパターンは、処理中に例外が発生した場合でも、リソースが常に適切に解放されることを保証します。

challenge icon

チャレンジ

簡単

try-with-resourcesの威力を実証するリソース管理システムを構築しましょう!接続とセッションの管理をシミュレートするカスタムのAutoCloseableクラスを作成し、それらが常に適切にクリーンアップされることを確認します。

コードは3つのファイルに分けて構成します:

  • Connection.java: データベース接続をシミュレートするクラスを作成します。ConnectionクラスはAutoCloseableを実装する必要があります。接続名(String)のためのプライベートフィールド、名前を受け取って[name] connection openedと出力するコンストラクタ、Executing on [name]: [sql]と出力するquery(String sql)メソッド、そして[name] connection closedと出力する必須のclose()メソッドが必要です。
  • Session.java: ユーザーセッションをシミュレートする別のAutoCloseableクラスを作成します。SessionクラスもAutoCloseableを実装する必要があります。ユーザー名(String)のためのプライベートフィールド、ユーザー名を受け取ってSession started for [username]と出力するコンストラクタ、[username] performed: [action]と出力するperformAction(String action)メソッド、そしてSession ended for [username]と出力するclose()メソッドを含めてください。
  • Main.java: リソース管理システムを完成させましょう!接続名、ユーザー名、実行するアクションの3つの入力を受け取ります。

    まず、単一のリソース管理を実演します。=== Single Resource ===と出力し、次にtry-with-resourcesを使用して、指定された名前でConnectionを作成します。tryブロックの中で、query("SELECT * FROM users")を呼び出します。ブロックが終了すると、接続は自動的に閉じられるはずです。

    次に、複数のリソースを実演します。空行を出力し、その後に=== Multiple Resources ===と出力します。try-with-resourcesを使用して、Connection(同じ名前を使用)とSession(ユーザー名を使用)の両方を、セミコロンで区切ってその順序で宣言します。tryブロックの中で、まず接続に対してquery("INSERT INTO logs")を呼び出し、次にセッションに対して入力されたアクションでperformActionを呼び出します。リソースが宣言の逆順で閉じられる様子を確認してください!

入力は、接続名(String)、ユーザー名(String)、アクション(String)の順で3つ受け取ります。

try-with-resourcesによって、明示的なcloseの呼び出しやfinallyブロックが不要になることに注目してください。例外が発生した場合でもリソースのクローズが保証され、複数のリソースは宣言とは逆の順序でクローズされます!

チートシート

try-with-resources 文は、ブロックが完了したときにリソースを自動的に閉じます。AutoCloseable を実装しているクラスであれば、どのようなクラスでも使用できます:

try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
    String line = reader.readLine();
}  // ここで自動的に閉じられます

セミコロンで区切ることで、複数のリソースを管理できます。これらは宣言された順序とは逆の順序で閉じられます:

try (FileReader fr = new FileReader("input.txt");
     BufferedReader br = new BufferedReader(fr)) {
    // 両方のリソースを使用する
}  // br が最初に閉じられ、次に fr が閉じられます

カスタムの auto-closeable クラスを作成するには、AutoCloseable インターフェースとその close() メソッドを実装します:

public class DatabaseConnection implements AutoCloseable {
    public DatabaseConnection() {
        System.out.println("Connection opened");
    }
    
    @Override
    public void close() {
        System.out.println("Connection closed");
    }
}

自分で試してみよう

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String connectionName = scanner.nextLine();
        String username = scanner.nextLine();
        String action = scanner.nextLine();
        
        // === 単一のリソース ===
        // TODO: ヘッダーを出力する
        // TODO: try-with-resources を使用して Connection を作成する
        // TODO: try ブロック内で query("SELECT * FROM users") を呼び出す
        
        // === 複数のリソース ===
        // TODO: 空行とヘッダーを出力する
        // TODO: Connection と Session の両方で try-with-resources を使用する
        // TODO: try ブロック内:
        //       - connection に対して query("INSERT INTO logs") を呼び出す
        //       - session に対して入力された action を引数に performAction を呼び出す
        
    }
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン