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