0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Effective Java 2-9:try-finallyよりもtry-with-resourceを選ぶ

Posted at

2-9:try-finallyよりもtry-with-resourceを選ぶ

要点

AutoCloseable を実装するリソースは try-with-resources を使う。
手動で finally で close() するより安全で、閉じるときに発生する例外で元の例外が上書きされる問題を防げる。

悪い例

// 悪い:finally の close が例外を投げると元の例外が失われる
void writeFileBad(Path path, byte[] data) throws IOException {
    OutputStream out = new FileOutputStream(path.toFile());
    try {
        out.write(data);
        // Suppose this throws IOException A
    } finally {
        // Suppose close() throws IOException B
        out.close(); // IOException B will propagate, A is lost
    }
}

問題点:

  • out.write(...) が例外 A を投げた場合、finally の out.close() が例外 B を投げると A が失われ、B が呼び出し元に伝わる。元の原因が分からなくなりデバッグやログが難しくなる。

  • 手動で suppressed を扱う(addSuppressed)などの実装は煩雑でミスしやすい。

良い例

 void writeFileGood(Path path, byte[] data) throws IOException {
    try (OutputStream out = new FileOutputStream(path.toFile())) {
        out.write(data);
        // If this throws IOException A, and close throws B,
        // A is thrown and B becomes a suppressed exception of A.
    }
}

利点:

  • out.close() が例外を投げても、元の例外(A)が主例外として投げられ、close の例外(B)は A.getSuppressed() に入る。元の原因を失わない。

  • 記述が短く、リソース解放漏れのミスが減る。

 
確認用コード(抑圧例外の取得):

  • try-with-resources は主原因(本体の例外)を失わないように配慮している。だが、close 時の例外情報も重要なので抑圧例外として残す。

  • ロギングや診断の際に、主例外だけでなく suppressed も出力しておくと「本体で何が起き、閉じるときに何が起きたか」が全部見えてデバッグしやすい。

try {
    writeFileGood(path, data);
} catch (IOException e) {
    for (Throwable suppressed : e.getSuppressed()) {
        System.err.println("Suppressed: " + suppressed);
    }
    e.printStackTrace();
}

完成形

class BrokenResource implements AutoCloseable {
    @Override
    public void close() throws IOException {
        throw new IOException("close failed (B)");
    }
    public void write() throws IOException {
        throw new IOException("write failed (A)");
    }
}

void writeFileGood() throws IOException {
    try (BrokenResource r = new BrokenResource()) {
        r.write(); // ここで例外 A が投げられる
    } // close() が呼ばれ例外 B が出るが、A が主で B は suppressed になる
}

public static void main(String[] args) {
    try {
        writeFileGood();
    } catch (IOException e) {
        for (Throwable suppressed : e.getSuppressed()) {
            System.err.println("Suppressed: " + suppressed);
        }
        e.printStackTrace();
    }
}

※複数リソースの場合
閉じる順序は 逆順(out.close() が先、その次に in.close())。
これは依存関係のあるリソースを安全に閉じるのに自然な順序。

try (
    InputStream in = new FileInputStream("in");
    OutputStream out = new FileOutputStream("out")
) {
    // do copy
}

try-with-resourceが扱えないケースと対策

1. リソースが AutoCloseable を実装していない

  • 対策:自分で AutoCloseable を実装するラッパを作る(閉じる処理を close() に移す)。
class LegacyWrapper implements AutoCloseable {
    private final LegacyResource r;
    LegacyWrapper(LegacyResource r) { this.r = r; }
    @Override public void close() { r.cleanup(); }
    // delegate methods...
}

2. 閉じるべきリソースが null の可能性がある

  • try-with-resources はリソース変数が null の場合に close() を呼ばない(NPE は起きない)ので基本的に安全。ただし close() 実装が null を許容しないなら注意。

まとめ

  • try-with-resources は 例外の扱い(特に close 時の例外) を正しく扱い、コードを簡潔にするので ほぼ常に推奨。
     
  • try/finally はレガシーや非 AutoCloseable なケースでしか使わない/使えない場面に限定する。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?