2018年11月14日 星期三

Java SE 入門教學 - 例外處理

更新時間:11/19/2018

前言

程式在執行時,有時候會碰到一些狀況使得程式崩潰,這是程式設計師最不想要遇到的狀況。所以 JAVA 提供一些方式可以處理這些狀況,讓程式更穩定。

一、例外的概念

例外指的是運行期出現的錯誤,也就是當程序開始執行以後執行期出現的錯誤。出現錯誤時觀察錯誤的名字和行號最為重要。

Java 例外是 Java 提供用於處理程序中異常的一種機制。

所謂錯誤是指在程序運行的過程中發生的一些例外事件(如:除0溢出,數組下標越界,所要讀取的文件不存在)。

設計良好的程序應該在例外發生時提供處理這些錯誤的方法,使得程序不會因為異常的發生而阻斷或產生不可預見的結果。

Java 程序的執行過程中如出現例外事件,可以生成一個例外物件實例,該例外物件實例封裝了例外事件的信息並將被提交給 Java 執行程序,這個過程稱為拋出(throw)例外。

當 Java 執行程序接收到例外物件實例時,會尋找能處理這一例外的代碼並把當前例外物件實例交給其處理,這一過程稱為捕獲(catch)例外。


二、例外的分類

  • Error:稱為錯誤,由 Java 虛擬機器生成並拋出,包括動態鏈接失敗、虛擬機器錯誤等,程序對其不做處理。
  • Exception:所有例外類別的父親,其子類別對應了各種各樣可能出現的例外事件,一般需要用戶顯示的聲明或捕獲。
  • RuntimeException:特殊的例外,如被 0 除、數組下標超出範圍等。其產生比較頻繁,處理麻煩,如果顯示的宣告或捕獲將會對程序可讀性和運行效率影響很大。因此由系統加入邏輯判斷來自動檢測,過濾可能的例外狀況。

▼ Throwable 相關類別的樹狀圖


三、例外的捕獲和處理

目的:不讓程式碼直接中斷

可以處理程式預期不到的錯誤。即使程式發生例外,程式碼也會正常結束,不像沒有處理例外的程式,則程式碰到例外就立即中斷。

五個關鍵字:try、catch、finally、throw、throws

捕獲和處理

try{

  // 可能拋出例外的語句

}catch( 例外類別1 物件變數1 ){

}catch( 例外類別2 物件變數2 ){

}catch( 例外類別3 物件變數3 ){

}finally{

}

  • try 代碼區塊包含可能產生例外的代碼。

  • try 代碼區塊後跟著有一個或多個 catch 代碼區塊。若使用多個 catch 子句,其參數列的例外類別又有繼承關係時,子類別必須先捕捉;否則無順序關係。

  • 每個 catch 代碼區塊聲明其能處理的一種特定類型的例外並提供處理的方法。
  • 當異常發生時,程序會中止當前的流程,根據獲取例外的類型去執行相應的 catch 代碼區塊。

  • finally 代碼區塊無論是否發生例外都會執行。
  • 若 try 區塊代碼包含 return 敘述,則 finally 子句會先執行然後再回傳值
  • 只有一種狀況會防止 finally 子句被執行,就是虛擬機器關閉(例如執行 System.exit() 方法,或是關機),這表示控制流程可能脫離正常執行順序。

在邏輯區塊拋出例外類別

throw someThrowableObject;
  • 只拋出一個 Throwable 類別或其子類別的例外類別。
  • 也可以拋出自定義的例外類別

在方法上宣告拋出例外類別

  • 可以拋出一個或多個 Exception 例外類別。
  • 可以同時包含 checked 與 unchecked exceptions。
  • 也可以拋出自定義的例外類別

當捕獲到異常以後一定要做出處理,哪怕是把這個異常的錯誤信息打印出來,這是一種良好的編程習慣。如果不處理,那就是把這個錯誤悄悄地隱藏起來了,可是這個錯誤依然是存在的,只不過看不到了而已。這是一種非常危險的編程習慣,絕對不能這樣做,捕獲到異常就一定要做出處理,實在處理不了就把異常拋出去,讓別的方法去處理。總之就是不能捕獲到異常之後卻又不做出相應的處理,這是一種非常不好的編程習慣。

任何方法往外拋能處理的例外的時候都有一種簡單的寫法:“throws Exception”,因為 Exception 類別是所有能處理的異常類的根基類,因此拋出 Exception 類就會拋出所有能夠被處理的異常類裡了。使用“throws Exception”拋出所有能被處理的異常之後,這些被拋出來的異常就是交給 JAVA 執行程序處理了,而處理的方法是把這些異常的相關錯誤堆棧信息全部打印出來。除了在做測試以外,在實際當中編程的時候,在 main 方法裡拋 Exception 是一個非常不好的編程習慣,應該使用 try-catch 去捕獲異常並處理掉捕穫後的異常。不能直接在 main 方法裡把 Exception 拋出去交給 JAVA 執行程式出力就完事了,這是一種不負責任的表現。如果想把程序寫得特別健壯,使用 try-catch 去捕獲異常並處理掉捕穫後的異常是必不可少的做法


四、try-catch-finally 語法

4.1 try 語句

try{......} 語句指定了一段代碼,該段代碼就是一次捕獲並處理例外的範圍。在執行過程中,該段代碼可能會產生並拋出一種或幾種類型的例外物件實例,它後面的 catch 語句要分別對這些異常做相應的處理。如果沒有例外產生,所有的 catch 代碼區塊都被略過不執行。

4.2 catch 語句

在 catch 語句區塊中是對異常進行處理的代碼,每個 try 語句區塊可以判隨一個或多個 catch 語句,用於處理可能產生的不同類型的例外物件實例。在 catch 中聲明的例外物件實例 catch(SomeException e) 封裝了異常事件發生的信息,在 catch 語句區塊中可以使用這個物件實例的一些方法獲取這些信息。

例如:

▵ getMessage()方法:用來得到有關異常事件的信息。
▵ printStackTrace()方法:用來跟蹤異常事件發生時執行的堆棧的內容。

4.3 finally 語句

finally 語句為異常處理提供一個統一的出口,使得在控制流程轉到程序的其他部分以前,能夠對程序的狀態做統一的管理。無論 try 所指定的程序區塊中是否拋出例外,finally 所指定的代碼都要被執行。

通常在 finally 語句中可以進行資源的清除工作,如:

▵ 關閉打開的文件
▵ 刪除臨時檔案。
▵ ...

測試例外發生時,會發生什麼樣的事情

▼ 故意把除數輸入0,執行後發生例外!。
ArithmeticException 是 RuntimeException 的子類別。


修正上述的程式碼

原本 RumtimeException 是不需要使用例外處理,這邊為測試程式碼,所以故意寫例外處理。
測試例外會不會被 catch 捕捉,並測試 finally 是不是一定會執行。

▼ 例外被捕捉,且一定會執行 finally。


catch 的例外類別,子類別一定要寫在父類別上方;否則會編譯錯誤(complier error!)

▼ 錯誤訊息說明「例外已經被捕捉了!」



當輸入浮點數,拋出例外 InputMismatchException

▼ inputMismatchException 不是靜態,所以要先 import。



五、throw 與 throws 用法

注意:重寫方法需要拋出與原方法所拋出例外類型一致或不拋出例外

簡單測試



六、使用自定義例外類別

使用自定義例外類別一般有如下步驟:

1. 通過繼承 java.lang.Exception 類別建立自己的例外類別。
2. 在方法適當的位置生成自定義例外的物件實例,並用 throw 語句拋出。
3. 在方法的宣告部分用 throws 語句聲明該方法可能拋出的例外。

範例:



七、斷言、警告(Assertion)用法

例外是程式中非預期的錯誤,例外處理是在這些錯誤發生時所採取的措施。

有些時候,您預期程式中應該會處於何種狀態,例如某些情況下某個值必然是多少,這稱之為一種斷言(Assertion),斷言有兩種情況:成立或不成立。當預期結果與實際執行相同時,斷言成立,否則斷言失敗。

Java 在 JDK 1.4 之後提供斷言陳述,有兩種使用的語法:

assert <boolean_expression>;
assert <boolean_expression> : <detail_expression>;

boolean_expression 如果為 true,則什麼事都不會發生,如果為 false,則會發生 java.lang.AssertionError,此時若採取的是第二個語法,則會將 detail_expression 的結果顯示出來,如果是個物件,則呼叫它的 toString() 顯示文字描述結果。


範例:

▼ 執行時必須加上-ea才會執行此語法
java -ea TestAssert 或 java -enableassertions TestAssert



八、總結

  • 記住 Throwable 相關類別的樹狀圖
  • 五個關鍵字:try, catch, finally, throw, throws
  • 先逮小的,再逮大的。有繼承關係的例外類別時,子類別必須要在父類別前先行捕捉。
  • 覆寫方法需要拋出與原方法所拋出的例外物件實例一致或者不拋出例外。

養成良好的編程習慣,不要把錯誤給吞噬掉(即捕獲到例外以後又不做出相應處理的做法,這種做法相當於是把錯誤隱藏起來了,可實際上錯誤依然還是存在的),也不要輕易地往外拋錯誤,能處理的一定要處理,不能處理的一定要往外拋。往外拋的方法有兩種,一種是在知道例外的類型以後,方法宣告時使用 throws 把例外往外拋,另一種是手動往外拋,使用 “throw + 例外物件實例” 你相當於是把這個例外物件實例拋出去了,然後在方法的宣告寫上要拋的那種例外類別。





沒有留言:

張貼留言