Java 故障安全異常處理
異常處理代碼必須保證其故障安全機制,其中一條重要的規則如下:
在
try-catch-finally
塊拋出的最後一個異常將會在調用堆棧中傳遞。
所有早期異常將會消失。
如果從一個catch
或finally
塊拋出一個異常,那麽這個異常可能會導致try
塊中捕獲的異常隱藏。這會在你試圖確定異常的原因時產生誤導。
下面是non-fail-safe
異常處理的經典示例:
InputStream input = null;
try {
input = new FileInputStream( "myFile.txt" );
/* do something with the stream */
}
catch ( IOException e ) {
throw new WrapperException( e );
}
finally {
try {
input.close();
}
catch ( IOException e ) {
throw new WrapperException( e );
}
}
如果FileInputStream
構造器拋出一個FileNotFoundException
異常,你認為會發生什麽?
首先會執行catch
塊,該塊只會重新拋出包裝在WrapperException
中的異常。
其次將執行finally
FileInputStream
構造器拋出了一個FileNotFoundException
異常,引用變量"input"將為null
。結果將是從finally
塊中拋出NullPointerException
異常。NullPointerException
不會被catch ( IOException e )
子句捕獲,所以它將會在調用堆棧中傳遞。而第一個 catch 塊中拋出的WrapperException
將會消失。
處理這種情況的正確方式是,再調用任何方法之前,先檢查在try
塊中分配的引用是否為null
。比如像下面這樣:
InputStream input = null;
try {
input = new FileInputStream("myFile.txt");
//do something with the stream
}
catch(IOException e) {
//first catch block
throw new WrapperException(e);
}
finally {
try {
if(input != null) input.close();
}
catch(IOException e){
//second catch block
throw new WrapperException(e);
}
}
但即使是這樣處理同樣還是會有問題,讓我們先假設"myFile.txt"文件存在,因此"input"引用現在指向一個有效的FileInputStream
。同樣我們也假設處理輸入流時引發了異常,這時第一個 catch 子句捕獲後處理並拋出WrapperException
,在將WrapperException
傳遞到調用堆棧之前,還要先執行finally
子句。如果input.close()
調用失敗,那麽它將會拋出IOException
並且被第二個 catch 子句捕獲並拋出WrapperException
,這時從第一個 catch 子句中拋出的WrapperException
再次消失,只有第二個 catch 拋出的WrapperException
才會被傳遞到調用堆棧中。
如你所見,故障安全(fail safe)異常處理並不總是無價值的。InputStream
處理示例甚至不是你可以遇到的最復雜的示例。JDBC 中的事務有更多錯誤的可能性。當嘗試提交、然後回滾,最後嘗試關閉連接時,可能會出現異常。所有這些可能出現的異常都應該由異常處理代碼來處理,因此,他們都不會使第一個被拋出異常消失。這樣做的一種方法是確保拋出的最後一個異常包含以前拋出的所有異常,這樣開發人員就可以了解錯誤原因。
BTW,Java 7 中的try-with-resources
特性使得實現故障安全異常處理變得更加容易。
原文鏈接:http://tutorials.jenkov.com/java-exception-handling/fail-safe-exception-handling.html
Java 故障安全異常處理