Java | Java 標準ライブラリ:try-with-resources

Java Java
スポンサーリンク

try-with-resources を一言でいうと

try-with-resources は、

「使い終わったら必ず close() しなきゃいけないものを、自動で確実に閉じてくれる try 文の書き方」

です。

ファイル、ソケット、DB 接続など、

  • 開いたまま忘れると、メモリや OS のリソースを食いつぶす
  • 例外が起きても、最後にちゃんと close() しないといけない

といった「お片付け必須のオブジェクト」を
安全・簡単に扱うための仕組みです。


なぜ try-with-resources が必要なのか(従来の書き方の問題点)

昔の典型パターン:finally で close する

try-with-resources 登場前(Java 6 まで)は、
ファイルを読むコードはたいていこんな形でした。

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

public class OldStyle {
    public static void main(String[] args) {
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new FileReader("data.txt"));
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (reader != null) {
                try {
                    reader.close();  // 忘れずに close する必要がある
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
Java

ここで注意しなければいけないポイントが多すぎます。

  • reader を先に null で宣言
  • finallynull チェック
  • close() も例外を投げるので、さらに try-catch が必要

「ファイルを開いて、1 行ずつ読みたい」だけなのに、
close 関連のコードがやたらと多いですよね。

しかも、ちょっと気を抜くと

  • finally を書き忘れる
  • close() の例外を握りつぶしてしまう

など、バグの温床になります。

「絶対に閉じ忘れてはいけない」という重圧

ファイル・ソケット・DB 接続などは、

  • 閉じ忘れると OS リソースが枯渇する
  • 長時間動くサーバーだと、少しの漏れが積み重なって死ぬ

という、かなりクリティカルな性質を持っています。

それを「人間が毎回がんばって finally に書く」というやり方は、
厳しすぎるし、バグを招きやすい。

そこで生まれたのが try-with-resources です。


try-with-resources の基本構文と動き

構文の形

Java 7 以降では、次のように書けます。

try (ResourceType res = new ResourceType(...)) {
    // res を使った処理
} catch (Exception e) {
    // 例外処理
}
Java

try の後ろの丸かっこの中で作られた「リソース」は、
そのブロックを抜けるときに、自動で close() されます。

ファイルを読む例を、try-with-resources で書き直してみます。

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

public class NewStyle {
    public static void main(String[] args) {
        try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } // ここを出るとき、自動的に reader.close() が呼ばれる
    }
}
Java

さっきの長い finally が、丸ごと消えました。

これだけで、

  • close の書き忘れがなくなる
  • null チェックが不要
  • close() 用の try-catch も不要

という、かなり大きなメリットが得られます。

具体的にいつ close されるか

流れとしては、次のようなイメージです。

  1. try ( ... ) の中でリソースを生成
  2. { ... } の中の処理を実行
  3. 正常終了でも、途中で例外が投げられても、ブロックを抜ける直前に close()

つまり、

  • while が全部回りきったあと
  • readLine()IOException が出て catch に飛ぶとき

どちらの場合でも、必ず close() されるわけです。


どんなものが「try-with-resources の対象」になれるのか

AutoCloseable / Closeable を実装しているクラス

try-with-resources で使えるのは、
java.lang.AutoCloseable インターフェースを実装しているクラスです。

ファイル関連でよく出てくる、

  • InputStream / OutputStream
  • Reader / Writer
  • java.sql.Connection / PreparedStatement / ResultSet

などは、いずれも AutoCloseable(またはサブインターフェースの Closeable)を実装しています。

だからこそ、

try (FileInputStream in = new FileInputStream("data.bin")) {
    ...
}
Java

try (Connection con = dataSource.getConnection()) {
    ...
}
Java

が、そのまま書けます。

自作クラスでも try-with-resources 対象にできる

自分で作ったクラスに対しても、
AutoCloseable を実装すれば対象にできます。

class MyResource implements AutoCloseable {
    void doWork() {
        System.out.println("作業中...");
    }

    @Override
    public void close() {
        System.out.println("close() が呼ばれました");
    }
}

public class CustomResourceExample {
    public static void main(String[] args) {
        try (MyResource res = new MyResource()) {
            res.doWork();
        }
    }
}
Java

実行すると、最後に必ず close() が呼ばれます。

内部でファイルやソケットを持つラッパークラスを自作する場合などに、
「片付けの責任」を自分のクラスに閉じ込めることができます。


複数リソースをまとめて扱う(順番にも意味がある)

try ( …; …; … ) で複数同時に管理

try-with-resources は、1 つだけでなく複数のリソースを並べられます。

import java.io.*;

public class MultiResourceExample {
    public static void main(String[] args) {
        try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
             BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {

            String line;
            while ((line = reader.readLine()) != null) {
                writer.write(line);
                writer.newLine();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
Java

ここでは、

  • ファイルを読む reader
  • 別のファイルに書く writer

の 2 つを、1 つの try-with-resources でまとめて管理しています。

close される順番

複数リソースがある場合、
逆順close() されます。

上の例でいうと、

  1. writer.close()
  2. reader.close()

という順で呼ばれます。

これは、「後から開いたものを先に閉じる」
という自然なルールです。

多くのケースでは意識する必要はありませんが、
例えば「内側のリソースが外側のリソースに依存している」場合などに、
この順序が重要になることがあります。


例外との関係:try 部分と close 部分の両方で失敗したら?

ここは少し深めの話ですが、
try-with-resources をちゃんと理解するうえで重要です。

普通の try/finally だと起きがちな「例外の飲み込み」

従来の try-finally だと、こんなことが起きます。

  • try の中で例外が投げられる
  • finally で close() したときに、さらに別の例外が投げられる

この場合、「どっちの例外を外に伝えるのか?」が問題になります。

下手をすると、

  • 元々の本命の例外が消えてしまう
  • close 中の例外がメインに見えてしまう

といった、デバッグしづらい状況になりがちです。

try-with-resources は「本命の例外」を優先する

try-with-resources では、

  • try ブロックの中で発生した例外
  • close() の中で発生した例外

のうち、「try 内の例外」が優先されます。

close 中の例外は、「サプレッシングされた例外」として
メインの例外にぶら下がる形になります(Throwable#getSuppressed() で取れる)。

初心者の段階では、

「try ブロック内で起きた上流側の例外が優先される」

くらいの理解で十分です。

重要なのは、
try-with-resources の方が例外情報を失いにくい
つまり、デバッグがしやすいということです。


よくあるパターンを try-with-resources で書き直してみる

ファイル全行のコピー(Reader / Writer)

BufferedReader + BufferedWriter を使った定番パターンは、
すでに見た通りかなりスッキリ書けます。

try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
     BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {

    String line;
    while ((line = reader.readLine()) != null) {
        writer.write(line);
        writer.newLine();
    }
}
Java

finally での close 忘れを気にしなくてよくなるだけでなく、
コードの「本質」(ここではコピー処理)が読みやすくなります。

InputStream / OutputStream でも同じ

バイナリのコピーも同様です。

try (InputStream in  = new FileInputStream("input.bin");
     OutputStream out = new FileOutputStream("output.bin")) {

    byte[] buf = new byte[8192];
    int len;
    while ((len = in.read(buf)) != -1) {
        out.write(buf, 0, len);
    }
}
Java

try-with-resources のおかげで、

  • read/write のループに集中できる
  • close の心配をほぼ忘れていい

という状態になります。


まとめ:try-with-resources を自分の中でどう位置づけるか

try-with-resources を初心者向けにまとめると、こうです。

  • 「開いたら必ず閉じなきゃいけない資源」を、自動で確実に close() してくれる try の書き方
  • try ( ... ) { ... } の丸かっこ内で宣言したリソースは、ブロックを抜けるとき必ず close() される
  • 対象は AutoCloseable を実装したクラス(ファイル、ソケット、DB 接続などが代表例)
  • 従来の try-finally より、コードが短く、安全で、例外情報も失われにくい
  • 「I/O や DB まわりで、新しく書くコードは全部 try-with-resources にする」くらいの気持ちでちょうどいい

タイトルとURLをコピーしました