使用 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 方法來處理異常屏蔽。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章