687
技術社區[雲棲]
基本的try-cathc-finally異常處理
這一小節概述了try-catch-finally 語句是怎樣處理錯誤的,文中的例子是Java的,但是同樣的規則也適用於C#。java和C#中異常的唯一區別就是C#中沒有已檢查異常。已檢查異常和未檢查異常將在後麵小節更加詳細地介紹。
程序中的異常表明一些錯誤或者異常情況發生了,異常如果沒有被處理,繼續程序流程是沒有意義的。一個方法可能會因為各種原因拋出異常,比如輸入參數是無效的(當期待獲得正數時輸入了負數等。)
調用棧
下麵介紹調用棧。調用棧是指從當前的方法開始一直到main方法處的方法調用序列。如果方法A調用方法B,方法B調用方法C,則調用棧如下所示:
當方法C返回後,調用棧僅僅包含A和B,如果方法B再調用方法D,則調用棧如下所示:
理解調用棧對理解異常傳播是非常重要的。從拋出異常的方法開始,異常根據調用棧傳播,直到調用棧中某個方法捕獲該異常。更多細節稍後討論。
拋出異常
如果一個方法要拋出異常,那麼要在方法簽名中聲明這個異常,然後在方法裏包含一條throw語句。下麵是一個例子:
1 |
public void divide( int numberToDivide, int numberToDivideBy)
|
2 |
throws BadNumberException{
|
3 |
if (numberToDivideBy == 0 ){
|
4 |
throw new BadNumberException( "Cannot divide by 0" );
|
6 |
return numberToDivide / numberToDivideBy;
|
當一個異常被拋出時,方法在拋出異常處停止運行。throw語句後的任何語句都將不會被執行了。在上麵這個例子中,如果拋出BadNumberException異常,那麼”return numberToDivide / numberToDivideBy;”這條語句將不會被執行。當異常被catch語句塊捕獲時,程序才會繼續執行。捕獲異常稍後解釋。
隻要在方法簽名中做了聲明,你可以拋出任何異常。你也可以創建自己的異常。異常是繼承自java.lang.Exception的標準java類或者任何其他的內置異常類。如果一個方法聲明拋出異常A,那麼方法拋出異常A的子類異常也是合法的。
捕獲異常
如果一個方法調用了另一個會拋出已檢查異常的方法,那麼這個調用方法必須要麼傳遞這個異常,要麼捕獲這個異常。捕獲異常是利用try-catch語句塊來實現的。下麵是一個例子:
01 |
public void callDivide(){
|
03 |
int result = divide( 2 , 1 );
|
04 |
System.out.println(result);
|
05 |
} catch (BadNumberException e) {
|
06 |
//do something clever with the exception
|
07 |
System.out.println(e.getMessage());
|
09 |
System.out.println( "Division attempt done" );
|
當拋出異常時,catch語句中的BadNumberException 類型參數e就代表從divide方法中拋出的異常。
如果try語句塊中任何被調用的方法和執行的語句都沒有拋出異常,catch語句塊僅僅被忽略,不會被執行。
如果try語句塊中拋出異常,例如在divide方法中拋出異常,調用方法(即callDrive方法)中的程序流將會像divide方法中的程序流一樣被中斷。程序流將在調用棧中某個能夠捕獲這個異常的catch語句塊處恢複。上麵例子中,如果一個異常從divide方法中拋出,那麼“System.out.println(result);” 語句將不會被執行。這時程序將會在catch (BadNumberException e) { }語句塊裏恢複執行。
如果在ctach語句塊裏拋出了一個沒有被捕獲的異常,那麼catch語句塊的運行將會像try語句中塊拋出異常一樣被中斷。
當catch語句塊執行完,程序將會執行catch語句塊後麵的所有語句。上麵例子中的”System.out.println(“Division attempt done”);” 語句將永遠會被執行。
異常傳播
你也沒有必要捕獲其他方法中拋出的異常。如果你在方法拋出異常的地方不能對異常做任何事,你可以讓方法根據調用棧將這個異常傳播到調用這個方法的其他方法處。如果你這樣做,調用這個將拋出異常方法的其他方法必須要在方法簽名中聲明拋出這個異常。下麵是callDivide()方法在這種情況下的例子:
1 |
public void callDivide() throws BadNumberException{
|
2 |
int result = divide( 2 , 1 );
|
3 |
System.out.println(result);
|
注意try-catch語句塊不見了,callDivide 方法聲明它會拋出一個BadNumberException異常。如果divide方法裏拋出一個異常,程序仍將被中斷。因此如果divide方法裏拋出一個異常,”System.out.println(result);” 語句將不會被執行。但是這樣的話callDivide 方法中(因為拋出異常而中斷的的)程序執行不會被恢複。異常會被傳播到調用callDivide的方法中。直到調用棧中的某個catch語句塊捕獲了這個異常,程序執行才會恢複。根據調用棧,從拋出異常的方法到捕獲異常的方法之間的所有方法的運行都將停止在異常拋出或者傳播的代碼處。
捕獲IOException示例
如果一個異常在try語句塊中被拋出,程序的順序執行將會被中斷,控製流將直接跳轉到catch語句塊。代碼將會因為異常在幾個地方被中斷:
01 |
public void openFile(){
|
03 |
// constructor may throw FileNotFoundException
|
04 |
FileReader reader = new FileReader( "someFile" );
|
07 |
//reader.read() may throw IOException
|
09 |
System.out.println(( char ) i );
|
12 |
System.out.println( "--- File End ---" );
|
13 |
} catch (FileNotFoundException e) {
|
14 |
//do something clever with the exception
|
15 |
} catch (IOException e) {
|
16 |
//do something clever with the exception
|
如果reader.read()方法調用拋出一個IO異常,下麵的System.out.println((char) i );語句不會被執行。最後的reader.close() 和System.out.println(“— File End —“);語句也不會被執行。程序直接調轉到catch(IOException e){ … }語句塊處。如果new FileReader(“someFile”); 構造器中調用拋出一個異常,那麼try語句塊中的代碼都不會被執行了。
傳播IOException示例
下麵的代碼仍然以上麵的方法為例,不過這裏沒有捕獲異常:
01 |
public void openFile() throws IOException {
|
02 |
FileReader reader = new FileReader( "someFile" );
|
06 |
System.out.println(( char ) i );
|
09 |
System.out.println( "--- File End ---" );
|
如果reader.read() 方法拋出一個異常,程序將會停止運行,異常根據調用棧傳播到調用openFile()方法的方法處。如果這個調用方法有try-catch語句塊,異常將在這裏被捕獲。如果這個調用方法也隻是拋出異常,這個調用方法的運行將在調用openFile()方法處中斷,異常根據調用棧往外傳播。異常就是這樣根據調用棧往外傳播,直到某個方法或者java虛擬機捕獲了這個異常。
Finally
你也可以在try-catch語句塊後增加一個finally語句塊。不論try還是catch語句塊中拋出異常,finally語句塊中的代碼將永遠執行。如果代碼裏的try或者catch語句塊中有個return語句,finally語句塊中的代碼將會在方法返回之前執行。finally示例見下:
01 |
public void openFile(){
|
02 |
FileReader reader = null ;
|
04 |
reader = new FileReader( "someFile" );
|
08 |
System.out.println(( char ) i );
|
10 |
} catch (IOException e) {
|
11 |
//do something clever with the exception
|
16 |
} catch (IOException e) {
|
17 |
//do something clever with the exception
|
20 |
System.out.println( "--- File End ---" );
|
不論try或者catch語句塊中是否有異常拋出,finally語句塊中的代碼都將被執行。這個例子表明不論try、catch語句塊中的程序運行情況如何,文件讀取始終會被關閉。
注意:如果finally語句塊中拋出一個異常,並且沒有被捕獲,finally語句塊就像try、catch語句塊拋出異常一樣也會被中斷運行。這就是為什麼上麵的例子中finally語句塊中的reader.close() 方法也被try-catch語句塊包裹原因:
05 |
} catch (IOException e) {
|
06 |
//do something clever with the exception
|
09 |
System.out.println( "--- File End ---" );
|
通過這樣,System.out.println(“— File End —“);語句將會永遠被執行。更準確地說是沒有未檢查異常被拋出時是這樣。更多關於已檢查異常和未檢測異常的知識將在後麵章節介紹。
你的程序中不需要同時又catch、finally語句塊。try語句塊後可以僅僅有catch語句塊或者finally語句塊,但是try語句塊後既沒有catch語句塊也沒有finally語句塊是不行的。下麵的代碼不會捕獲異常而是讓異常根據調用棧往外傳播,但是由於finally語句塊的存在,即是有異常拋出,程序仍舊能關閉打開的文件。
01 |
public void openFile() throws IOException {
|
02 |
FileReader reader = null ;
|
04 |
reader = new FileReader( "someFile" );
|
08 |
System.out.println(( char ) i );
|
14 |
} catch (IOException e) {
|
15 |
//do something clever with the exception
|
18 |
System.out.println( "--- File End ---" );
|
注意上麵catch語句塊不見了。
捕獲異常還是傳播異常?
你可能疑問:程序中拋出的異常是應該捕獲還是讓其傳播?這取決於具體情況。在許多應用程序中,除了告訴用戶請求操作失敗,你不能對異常做其他更多事情。在這種情況下,你可以在調用棧中的第一個方法裏捕獲所有的、或者大多數的異常。在傳播異常時,你仍然可以處理異常(通過finally語句)。比如,在一個web應用程序中的數據庫連接出現了錯誤,即使你所能做的隻是告訴用戶這個操作失敗,你仍然應該在finally語句中關閉這個數據庫連接。最終如何處理異常也取決於你的程序中拋出的是已檢查異常還是未檢查異常。關於已檢查異常、未檢查異常,後麵將有更多文章介紹。
最後更新:2017-05-23 09:31:33