JDK1.7之后,引入了try-with-resources,使得關閉資源操作無需層層嵌套在finally中,代碼簡潔不少,本質是一個語法糖,能夠使用try-with-resources關閉資源的類,必須實現AutoCloseable接口。
1.7版本之前,傳統的關閉資源操作如下:
public static void main(String[] args){
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream("file.txt");
fileInputStream.read();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
assert fileInputStream != null;
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
可以看到,為了確保資源關閉正常,需要finall中再嵌入finally,try中打開資源越多,finall嵌套越深,可能會導致關閉資源的代碼比業務代碼還要多。
但是使用了try-with-resources語法后,上面的例子可改寫為:
try(FileInputStream fileInputStream1 = new FileInputStream("file.txt")){
fileInputStream1.read();
} catch (IOException e) {
e.printStackTrace();
}
如何判讀資源是否真的被關閉了呢,我們手寫個Demo:
實現AutoCloseable的資源類
class MyResource implements AutoCloseable{
public void open(){
System.out.println("resource is open!");
}
@Override
public void close() throws Exception {
System.out.println("resource is close!");
}
}
調用方:
public static void main(String[] args){
try(MyResource myResource = new MyResource()){
myResource.open();
} catch (Exception e) {
e.printStackTrace();
}
}
輸出如下,可以看到close方法被自動調用了,
resource is open!
resource is close!
底層原理是什么呢,看一下編譯后的class文件:
try {
MyResource myResource = new MyResource();
Throwable var2 = null;
try {
myResource.open();
} catch (Throwable var12) {
var2 = var12;
throw var12;
} finally {
if (myResource != null) {
if (var2 != null) {
try {
myResource.close();
} catch (Throwable var11) {
var2.addSuppressed(var11);
}
} else {
myResource.close();
}
}
}
} catch (Exception var14) {
var14.printStackTrace();
}
}
很明顯,編譯器生成了finally代碼塊,並在其中調用了close 方法,同1.7之前的關閉資源操作的實現原理是相同的,但是可以看到,這里多調用了一個addSuppressed方法,這么做其實是為了處理異常屏蔽,什么是異常屏蔽,首先,我們先修改一下剛剛的Demo,使資源類在open和close方法中拋出異常,並且使用1.7之前的關閉資源的方法,資源類以及調用方代碼修改如下:
public void open() throws IOException {
System.out.println("resource is open!");
throw new IOException("open() exception!");
}
@Override
public void close() throws Exception {
System.out.println("resource is close!");
throw new IOException("close() exception!");
}
public static void main(String[] args) throws Exception {
MyResource myResource = null;
try{
myResource = new MyResource();
myResource.open();
}finally {
try {
myResource.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
控制台打印如下:
open方法拋出的異常被自動忽略了,而異常信息丟失將導致程序調試困難,所以try-with-resources語法中加入了addSuppressed處理異常屏蔽,現在修改Demo為使用try-with-resource關閉資源,調用方代碼如下:
public static void main(String[] args) throws Exception {
try(MyResource myResource = new MyResource()){
myResource.open();
}
控制台打印如下
異常信息中多了提示:close方法中拋出的異常被open方法中拋出的異常抑制了。
其他問題:使用try-catch-resources,並不能完全保證資源被關閉,在javaBIO中,使用了大量的裝飾器模式,調用裝飾類的close方法時實際是在調用其中包裹的流的close方法,但是在調用包裹的流的close方法時,裝飾類還做了一些其他的操作,如果這些操作出現異常,將導致包裹流的close方法被跳過,資源沒有被正確關閉,正確的方式是在try中單獨聲明底層資源類以及裝飾類,這樣就可以保證,每個類的close方法都被調用。