使用 try-with-resources 關閉資源

JDK7 引入了 try-with-resources,為資源關閉提供了更為方便簡潔的方法。以一段簡單的 BIO 服務端代碼為例,使用傳統方式關閉資源:

public static void main(String[] args) {
    ServerSocket serverSocket = null;
    Socket socket = null;
    InputStream inputStream = null;
    try {
        serverSocket = new ServerSocket(3000);
        while (true) {
            socket = serverSocket.accept();
            inputStream = socket.getInputStream();

            byte[] bytes = new byte[5];
            int count = inputStream.read(bytes);
            while (count != -1) {
                for (int index = 0; index < count; index++) {
                    System.out.print((char) bytes[index]);
                }
                count = inputStream.read(bytes);
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            serverSocket.close();
            socket.close();
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

可見,為確保資源正確關閉,需要在 finall 中再嵌入 try,打開的資源越多,嵌套也越多,最終可能會出現關閉資源的代碼比業務代碼還要多的情況。另外,傳統方式還存在異常屏蔽的問題。在上例故意制造一個錯誤,將監聽端口號改為 -1,運行服務端時提示如下:

Exception in thread "main" java.lang.NullPointerException
    at com.example.TraditionalTry.main(TraditionalTry.java:32)

該異常指出 finally 塊的 serverSocket.close(); 發出空指針異常,并沒有指出問題真正所在代碼行,即 serverSocket = new ServerSocket(-1);,不利于問題的定位和排查。

為解決上述問題,使用 try-with-resources 改寫上例,不僅使代碼簡潔清晰,而且不會出現異常屏蔽的情況,能夠準確指示錯誤所在:

public static void main(String[] args) throws Exception {
    try (ServerSocket serverSocket = new ServerSocket(3000)) {
        while (true) {
            try (Socket socket = serverSocket.accept()) {
                try (InputStream inputStream = socket.getInputStream()) {

                    byte[] bytes = new byte[5];
                    int count = inputStream.read(bytes);
                    while (count != -1) {
                        for (int index = 0; index < count; index++) {
                            System.out.print((char) bytes[index]);
                        }
                        count = inputStream.read(bytes);
                    }
                }
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

為進一步驗證上述寫法,創建兩個實現 AutoCloseable 接口的簡單資源類。覆蓋后的 close() 方法,在提示關閉信息后均發出異常;Outer 類額外有一個提示信息后也會發出異常的 show() 方法:

注:能夠使用 try-with-resources 關閉資源的類,必須實現 AutoCloseable 接口。

class Outer implements AutoCloseable {

    public void show() {
        System.out.println("show is running!");
        throw new RuntimeException("Outer show Exception!");
    }

    @Override
    public void close() throws Exception {
        System.out.println("Outer close!");
        throw new RuntimeException("Outer close Exception!");
    }
}

class Inner implements AutoCloseable {

    @Override
    public void close() throws Exception {
        System.out.println("Inner close!");
        throw new RuntimeException("Inner close Exception!");
    }
}

調用:

public static void main(String[] args) {
    try (Outer outer = new Outer()) {
        try (Inner inner = new Inner()) {
            System.out.println("other code...");
            outer.show();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

輸出如下。可見資源都已經關閉,被屏蔽的異常也已經顯示出來:

other code...
show is running!
Inner close!
Outer close!
java.lang.RuntimeException: Outer show Exception!
    at com.example.Outer.show(TestTryResources.java:20)
    at com.example.TestTryResources.main(TestTryResources.java:8)
    Suppressed: java.lang.RuntimeException: Inner close Exception!
        at com.example.Inner.close(TestTryResources.java:35)
        at com.example.TestTryResources.main(TestTryResources.java:6)
    Suppressed: java.lang.RuntimeException: Outer close Exception!
        at com.example.Outer.close(TestTryResources.java:26)
        at com.example.TestTryResources.main(TestTryResources.java:5)

關于 try-with-resources 的底層實現原理,可以看看編譯后的 class 文件:

public static void main(String[] args) {
        try {
            Outer outer = new Outer();

            try {
                Inner inner = new Inner();

                try {
                    System.out.println("other code...");
                    outer.show();
                } catch (Throwable var7) {
                    try {
                        inner.close();
                    } catch (Throwable var6) {
                        var7.addSuppressed(var6);
                    }

                    throw var7;
                }

                inner.close();
            } catch (Throwable var8) {
                try {
                    outer.close();
                } catch (Throwable var5) {
                    var8.addSuppressed(var5);
                }

                throw var8;
            }

            outer.close();
        } catch (Exception var9) {
            var9.printStackTrace();
        }

    }

和傳統關閉資源操作的實現原理一樣,編譯器生成了 finally 代碼塊,并在其中調用了 close 方法,除此之外,還多調用了一個 addSuppressed 方法來處理異常屏蔽。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容