2019獨角獸企業重金招聘Python工程師標準>>>
1、舊社會
Java里,對于文件操作IO流、數據庫連接等開銷非常昂貴的資源,用完之后必須及時通過close方法將其關閉,否則資源會一直處于打開狀態,直至程序停止,增加系統負擔。
關閉資源的常用方式就是在finally塊里是釋放,即調用close方法。比如,我們經常會寫這樣的代碼:
public static void main(String[] args) {BufferedReader br = null;try {String line;br = new BufferedReader(new FileReader("d:\\testing.txt"));while ((line = br.readLine()) != null) {System.out.println(line);}} catch (IOException e) {// handle exception} finally {try {if (br != null) {br.close();}} catch (IOException ex) {// handle exception}}
}
可以看出,為了關閉資源以及處理關閉資源時可能出現的異常,不得不寫一大推代碼。
2、新時代
2.1 使用新寫法
從Java 7開始,jdk提供了一種更好的方式關閉資源,使用try-with-resources語句,改寫一下上面的代碼,效果如下:
public static void main(String[] args) {try(BufferedReader br = new BufferedReader(new FileReader("d:\\testing.txt"))) {String line;while ((line = br.readLine()) != null) {System.out.println(line);}} catch (IOException e) {// handle exception}
}
清爽了很多是不是? 但是,有沒有一點不安呢?
2.2 新問題
原來釋放資源的時候如果發生異常,我們可以在finally塊中catch新異常,然后繼續處理。但是新方式沒有了finally塊,異常是如何拋出的?如果關閉資源時發生異常怎么辦?我們怎么處理?
從文檔上可以找到這樣一段描述:
If an exception is thrown from the try block and one or more exceptions are thrown from the try-with-resources statement, then those exceptions thrown from the try-with-resources statement are suppressed. You can retrieve these suppressed exceptions by calling the Throwable.getSuppressed method from the exception thrown by the try block.
意思是:如果 try 塊拋出異常并且 try-with-resources 語句拋出一個或多個異常,那么從 try-with-resources 語句中拋出的異常將會被忽略。你可以通過調用由 try塊拋出的異常的Throwable.getSuppressed 方法檢索這些被忽略的異常信息。
在明確一點說就是:
- 如果try塊異常,catch到的是try塊拋出的異常;
- 如果try塊正常,close異常,catch到的是close拋出的異常;
- 如果try塊異常,close也異常,catch到的是try塊拋出的異常,close異常被忽略。
基于這幾種情況,我們做幾個測試分別驗證一下:
2.2.1 try塊異常
大家都懂,略。
2.2.2 try塊正常,close異常
public class TestTryWithResources {public static void main(String[] args) {try (MyResource resource = new MyResource()) {} catch (Exception e) {System.out.println("捕獲異常: " + e.getMessage());}}
}/*** 自定義一個資源類,close時拋出異常** ps:只有實現AutoCloseable或Closeable接口的對象才能用try-with-resources*/
class MyResource implements AutoCloseable {@Overridepublic void close() throws Exception {System.out.println("執行close方法,釋放資源");throw new Exception("釋放資源異常");}
}
執行結果為: 執行close方法,釋放資源 捕獲異常: 釋放資源異常
即,catch到的是close方法拋出的異常
2.2.3 try塊異常,close也異常
public class TestTryWithResources {public static void main(String[] args) {try (MyResource resource = new MyResource()) {throw new Exception("try塊異常");} catch (Exception e) {System.out.println("捕獲異常: " + e.getMessage());// 找到被忽略的異常Throwable[] ts = e.getSuppressed();for(Throwable t : ts) {System.out.println("被忽略的異常"+ t.getMessage());}}}
}/*** 自定義一個資源類,close時拋出異常*/
class MyResource implements AutoCloseable {@Overridepublic void close() throws Exception {System.out.println("執行close方法,釋放資源");throw new Exception("釋放資源異常");}
}
執行結果: 執行close方法,釋放資源 捕獲異常: try塊異常 被忽略的異常: 釋放資源異常
即,catch到的是try塊中的異常,釋放資源時產生的異常被忽略了。
2.3 實踐中的問題
實際上,很多時候try塊中的異常和close方法拋出的異常是同一類型的。比如流、網絡等不論是try塊還是close方法都是拋出IOException,我們該怎么辦? 最佳實踐:按異常棧最底層的方法名判斷。如果是close方法拋出的異常,就是關閉資源時產生的。 注:我們的方法名不能叫close
public class TestTryWithResources {public static void main(String[] args) {try (MyResource resource = new MyResource()) {throw new Exception("try塊異常");} catch (Exception e) {if(!"close".equals(e.getStackTrace()[0].getMethodName())){System.out.println("處理業務異常");}}}
}/*** 自定義一個資源類,close時拋出異常*/
class MyResource implements AutoCloseable {@Overridepublic void close() throws Exception {System.out.println("執行close方法,釋放資源");throw new Exception("釋放資源異常");}
}