Effective Java 3 rdエントリ9 try-with-resourcesはtry-finallyより優れている

3644 ワード

Javaライブラリにはcloseメソッドを手動で呼び出す必要がある多くのリソースが含まれています.このような例としては、InputStream、OutputStream、javaが挙げる.sql.Connection.リソースを閉じることはクライアントによって無視されることが多く、恐ろしいパフォーマンスの結果が予想されます.これらのリソースの多くはfinalizerをセキュリティとして使用していますが、finalizerはうまく機能しません(エントリ8).
歴史上、try-finally文は、例外に直面しても返されても、リソースを正しく閉じる必要があります.
// try-finally -             !
static String firstLineOfFile(String path) throws IOException { 
    BufferedReader br = new BufferedReader(new FileReader(path)); 
    try { 
        return br.readLine(); 
    } finally { 
        br.close(); 
    } 
}

これはあまり悪くないように見えますが、2番目のリソースを追加するとさらに悪くなります.
//           ,try-finally     ! 
static void copy(String src, String dst) throws IOException {       
    try { 
        InputStream in = new FileInputStream(src); 
        try { 
            OutputStream out = new FileOutputStream(dst); 
            byte[] buf = new byte[BUFFER_SIZE]; 
            int n; 
            while ((n = in.read(buf)) >= 0) 
                out.write(buf, 0, n); 
        } finally { 
            out.close(); 
        } 
    } finally { 
        in.close(); 
    }
}

これは信じられないかもしれませんが、優秀なプログラマーは多くの時間でこれを間違えます.最初は、Java Puzzzlers[Bloch 05]88ページでこれを間違え、何年も誰も気づかなかった.実際,2007年にはJavaライブラリのcloseメソッドの使用が3分の2間違っていた.
try-finally文でリソースの正しいコードを閉じます.前の2つのコードの例のように、微妙な欠陥さえあります.2つのtryコードブロックとfinallyコードブロックのコードが例外を放出する可能性があります.たとえば、firstLineOfFileメソッドでは、下位オブジェクトデバイスの失敗によりreadLineを呼び出すと異常が放出され、closeメソッドの呼び出しは同じ理由で失敗する可能性があります.これらの場合,2番目の異常は1番目を完全に覆った.異常スタック情報には最初の異常の記録はなく、実際のシステムではデバッグが非常に複雑になる可能性があります.通常、この問題を診断するために、最初の異常は私たちが見たいものです.1つ目の異常のために2つ目を抑制するには、このようなコードを書くことは可能ですが、実際には、誰もそんなことをしていません.これはうるさいからです.
Java 7がtry-with-resource文[JLS,14.20.3]を導入すると,これらの問題はすべて解決した.この構造で使用するには、リソースが空のcloseメソッドを返すAutoCloseableインタフェースを実装する必要があります.Javaライブラリとサードパーティライブラリの多くのクラスとインタフェースがAutoCloseableを実装または拡張しました.リソースを閉じる必要があるクラスを作成する場合は、クラスもAutoCloseableを実装する必要があります.
次に、try-with-resourceを使用した最初の例を示します.
// try-with-resources -          !
static String firstLineOfFile(String path) throws IOException {
    try (BufferedReader br = new BufferedReader( 
            new FileReader(path))) {
        return br.readLine();
    } 
}

次に、try-with-resourceを使用する2番目の例を示します.
//      try-with-resources -      
static void copy(String src, String dst) throws IOException { 
    try (InputStream in = new FileInputStream(src);
            OutputStream out = new FileOutputStream(dst)) { 
        byte[] buf = new byte[BUFFER_SIZE]; 
        while ((n = in.read(buf)) >= 0) 
            out.write(buf, 0, n); 
        }
}

try-with-resourceバージョンは、従来よりも短く、読み取り可能であるだけでなく、より多くの診断性を提供しています.firstLineOfFileメソッドを考慮します.異常が2つのreadLine呼び出しと(非表示の)closeメソッドによって投げ出されると,前の異常のために後の抑制が行われる.実際、あなたが本当に見たい異常を守るために、複数の異常を抑制することができます.これらの抑制の異常は破棄されず、スタック情報に印刷され、抑制されたことを注釈で説明します.getSuppressedメソッドでプログラミングで取得することもでき、Java 7にこのメソッドがThrowableに追加されました.
try-with-resource文にcatch句を置くことができます.try-finally文に置くことができるように.これにより、他のネストでコードを汚染することなく、異常を処理することができます.特別な例として、次のfirstLineOfFileメソッドのバージョンがあります.例外は放出されませんが、ファイルを開いたり読み込んだりできない場合は、デフォルト値を返します.
//  catch   try-with-resource 
static String firstLineOfFile(String path, String defaultVal) { 
    try (BufferedReader br = new BufferedReader( 
            new FileReader(path))) { 
        return br.readLine(); 
    } catch (IOException e) { 
        return defaultVal; 
    } 
}

この授業ははっきりしています.閉じなければならないリソースに関連する場合、try-finallyではなくtry-with-resourceを優先的に使用します.このようなコードはより短く、明確であり、発生した異常もより役に立ちます.閉じなければならないリソースを使用するコードについて、try-with-resource文は、try-finallyを使用することは実際には不可能です.