前言
程式在執行時,有時候會碰到一些狀況使得程式崩潰,這是程式設計師最不想要遇到的狀況。所以 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{ } |
|
在邏輯區塊拋出例外類別:
throw someThrowableObject;
|
|
在方法上宣告拋出例外類別:
xxxxxxxxxx
<modifier> <returnType> nameOfMethod (Parameter list)
throws someExceptionObject1, someExceptionObject2, ... {
//邏輯區段
}
- 可以拋出一個或多個 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 語句中可以進行資源的清除工作,如:
▵ 關閉打開的文件
▵ 刪除臨時檔案。
▵ ...
測試例外發生時,會發生什麼樣的事情

xxxxxxxxxx
import java.util.Scanner;
class TestExcept{
public static void main(String[] args){
int a=0, b=0, div=0, mod=0;
Scanner sc = new Scanner(System.in);
System.out.println("請輸入被除數:");
a = sc.nextInt();
System.out.println("請輸入除數:");
b = sc.nextInt();
div = a/b;
mod = a%b;
System.out.println(a + "/" + b + "=" + div + "..." + mod);
}
}
▼ 故意把除數輸入0,執行後發生例外!。
ArithmeticException 是 RuntimeException 的子類別。

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

xxxxxxxxxx
import java.util.Scanner;
class TestExcept{
public static void main(String[] args){
int a=0, b=0, div=0, mod=0;
Scanner sc = new Scanner(System.in);
System.out.println("請輸入被除數:");
a = sc.nextInt();
System.out.println("請輸入除數:");
b = sc.nextInt();
try{
div = a/b;
mod = a%b;
System.out.println(a + "/" + b + "=" + div + "..." + mod);
}catch(ArithmeticException e1){
System.out.println("系統錯誤:" + e1.getMessage());
System.out.println("錯誤原因:除數為0");
}catch(Exception e2){
System.out.println("系統錯誤:" + e2.getMessage());
}finally{
System.out.println(a + "/" + b + "=" + div + "..." + mod);
}
}
}
▼ 例外被捕捉,且一定會執行 finally。

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

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

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

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

xxxxxxxxxx
import java.util.Scanner;
import java.util.InputMismatchException;
class TestExcept{
public static void main(String[] args){
int a=0, b=0, div=0, mod=0;
try{
Scanner sc = new Scanner(System.in);
System.out.println("請輸入被除數:");
a = sc.nextInt();
System.out.println("請輸入除數:");
b = sc.nextInt();
}catch(InputMismatchException e1){
System.out.println("錯誤原因:輸入非整數數值");
}catch(Exception e2){
System.out.println("系統錯誤:" + e2.getMessage());
}
try{
div = a/b;
mod = a%b;
System.out.println(a + "/" + b + "=" + div + "..." + mod);
}catch(ArithmeticException e1){
System.out.println("系統錯誤:" + e1.getMessage());
System.out.println("錯誤原因:除數為0");
}catch(Exception e2){
System.out.println("系統錯誤:" + e2.getMessage());
}finally{
System.out.println(a + "/" + b + "=" + div + "..." + mod);
}
}
}

五、throw 與 throws 用法
注意:重寫方法需要拋出與原方法所拋出例外類型一致或不拋出例外
簡單測試
xxxxxxxxxx
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class TestExcept2 {
/**
* 任何方法往外拋能處理的異常的時候都有一種簡單的寫法:“throws Exception”,
* 因為Exception類是所有能處理的異常類的根基類,因此拋出Exception類就會拋出所有能夠被處理的異常類裡了。
* 使用“throws Exception”拋出所有能被處理的異常之後,這些被拋出來的異常就是交給JAVA運行時系統處理了,
* 而處理的方法是把這些異常的相關錯誤堆棧信息全部打印出來。
* @throws Exception
*/
void fn() throws Exception {
}
/**
* 在知道異常的類型以後,方法聲明時使用throws把異常往外拋
* @param i
* @throws ArithmeticException
*/
void m1(int i) throws ArithmeticException {
}
void m2(int i) {
if (i == 0) {
//這種做法就是手動拋出異常,使用“throw+new出來的異常對象”就可以把這個異常對象拋出去了。
//這裡是new了一個異常對象,在構建這個對象的時候還可以指定他相關的信息,如這裡指明了異常信息“i不能等於0”
//這個對象拋出去的時候使用getMessage()方法拿到的就是“i不能等於0”這種信息。
throw new ArithmeticException("i不能等於0");
}
}
/**
* 正常情況下如果這裡不寫try……catch語句那麼程序編譯時一定會報錯,
* 因為這裡有可能會產生兩個個必須要處理的異常:FileNotFoundException和IOException。
* 但由於在聲明方法f()時已經使用throws把可能產生的這兩個異常拋出了,
* 所以這裡可以不寫try……catch語句去處理可能會產生的異常。
* f()方法把拋出的異常交給下一個要調用它的方法去處理
* @throws FileNotFoundException
* @throws IOException
*/
void f() throws FileNotFoundException, IOException {
//這裡有可能會產生FileNotFoundException異常
FileInputStream fis = new FileInputStream("MyFile.txt");
//這裡有可能會產生IOException異常
int b = fis.read();
while (b != -1) {
System.out.println((char)b);
b = fis.read();
}
}
/**
* 在f2()方法裡面調用f()方法時必須要處理f()方法拋出來的異常,
* 當然,如果f2()方法也沒有辦法處理f()方法拋出來的異常,那麼f2()方法也可以使用throws把異常拋出,
* 交給下一個調用了f2()的方法去處理f()方法拋出來的異常。
* 這裡f2()調用f()方法時,選擇不處理f()方法中可能拋出的異常,將異常繼續拋出
* @throws Exception
*/
void f2() throws Exception {
f();
}
/**
* f3方法調用f方法捕獲f()方法拋出的2個異常並進行處理
*/
void f3() {
try {
f();
} catch (FileNotFoundException e) {
System.out.println(e.getMessage());//處理的方法是把錯誤信息打印出來
} catch (IOException e) {
e.printStackTrace();//處理的方法是使用printStackTrace()方法把錯誤的堆棧信息全部打印出來。
}
}
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("MyFile.txt");
int b = fis.read();//這個有可能會拋出IOException異常
while (b != -1) {
System.out.println((char)b);
b = fis.read();
}
} catch (FileNotFoundException e) {
//使用catch捕獲FileNotFoundException類異常的異常對象e。並讓異常對象e自己調用printStackTrace方法打印出全部的錯誤信息
e.printStackTrace();
} catch (IOException e) {
//再次使用catch捕獲IOException類的異常對象e,並讓異常對象e自己調用getMessage()方法將錯誤信息打印出來。
System.out.println(e.getMessage());
}finally{
try {
/**
* 前面已經把一個文件打開了,不管打開這個文件時有沒有錯誤發生,即有沒有產生異常,最後都一定要把這個文件關閉掉,
* 因此使用了finally語句,在finally語句裡面不管前面這個文件打開時是否產生異常,在finally這裡執行in.close()都能把這個文件關閉掉,
* 關閉文件也有可能會產生異常,因此在finally裡面也使用了try……catch語句去捕獲有可能產生的異常。
*/
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

六、使用自定義例外類別
使用自定義例外類別一般有如下步驟:
1. 通過繼承 java.lang.Exception 類別建立自己的例外類別。
2. 在方法適當的位置生成自定義例外的物件實例,並用 throw 語句拋出。
3. 在方法的宣告部分用 throws 語句聲明該方法可能拋出的例外。
範例:
xxxxxxxxxx
import java.text.MessageFormat;
/**
* 自定義的一個例外類別 MyException,且是從 Exception 類別繼承而來
*/
class MyException extends Exception {
private int id;
/**
* 自定義例外類別的構造方法
* @param message
* @param id
*/
public MyException(String message, int id) {
super(message); //調用父類別 Exception 的構造方法
this.id = id;
}
/**
* 獲取異常的代碼
* @return
*/
public int getId() {
return id;
}
}
class TestMyException {
//throws MyException,拋出我們自定義的MyException類的異常。
public void regist(int num) throws MyException {
if (num < 0) {
//使用throw手動拋出一個MyException類的異常對象。
throw new MyException("人數為負值,不合理", 1);
}
/**
* 注意:當我們拋出了異常之後,
* System.out.println(MessageFormat.format("登記人數:{0}",num));是不會被執行的。
* 拋出異常之後整個方法的調用就結束了。
*/
System.out.println(MessageFormat.format("登記人數:{0}",num));
}
public void manage() {
try {
regist(-100);
} catch (MyException e) {
System.out.println("登記失敗,錯誤碼:"+e.getId());
e.printStackTrace();
}
System.out.println("操作結束");
}
public static void main(String[] args) {
TestMyException t = new TestMyException();
t.manage();
}
}
七、斷言、警告(Assertion)用法
例外是程式中非預期的錯誤,例外處理是在這些錯誤發生時所採取的措施。
有些時候,您預期程式中應該會處於何種狀態,例如某些情況下某個值必然是多少,這稱之為一種斷言(Assertion),斷言有兩種情況:成立或不成立。當預期結果與實際執行相同時,斷言成立,否則斷言失敗。
Java 在 JDK 1.4 之後提供斷言陳述,有兩種使用的語法:
assert <boolean_expression> : <detail_expression>;
boolean_expression 如果為 true,則什麼事都不會發生,如果為 false,則會發生 java.lang.AssertionError,此時若採取的是第二個語法,則會將 detail_expression 的結果顯示出來,如果是個物件,則呼叫它的 toString() 顯示文字描述結果。
範例:
▼ 執行時必須加上-ea才會執行此語法
java -ea TestAssert 或 java -enableassertions TestAssert
xxxxxxxxxx
import java.util.*;
class TestAssert{
public static void main(String[] arg){
int score=0;
Scanner sc=new Scanner(System.in);
System.out.println("請輸入分數:");
score=sc.nextInt();
assert(score>=0 && score<=100):"分數錯誤";
System.out.print(score+"分 成績");
switch(score/10){
case 10:
case 9:
System.out.println("A");
break;
case 8:
System.out.println("B");
break;
case 7:
System.out.println("C");
break;
case 6:
System.out.println("D");
break;
default:
System.out.println("E");
}
}
}

八、總結
- 記住 Throwable 相關類別的樹狀圖
- 五個關鍵字:try, catch, finally, throw, throws
- 先逮小的,再逮大的。有繼承關係的例外類別時,子類別必須要在父類別前先行捕捉。
- 覆寫方法需要拋出與原方法所拋出的例外物件實例一致或者不拋出例外。
養成良好的編程習慣,不要把錯誤給吞噬掉(即捕獲到例外以後又不做出相應處理的做法,這種做法相當於是把錯誤隱藏起來了,可實際上錯誤依然還是存在的),也不要輕易地往外拋錯誤,能處理的一定要處理,不能處理的一定要往外拋。往外拋的方法有兩種,一種是在知道例外的類型以後,方法宣告時使用 throws 把例外往外拋,另一種是手動往外拋,使用 “throw + 例外物件實例” 你相當於是把這個例外物件實例拋出去了,然後在方法的宣告寫上要拋的那種例外類別。
沒有留言:
張貼留言