此 文件已有 新 版本!
在Java中如果應用程式發生錯誤,會將錯誤相關資訊以例外物件包裝後再丟出,例外可能是由JVM產生,或者是你自行丟出,無論如何,以例外物件的形式, 給了你機會來面對與操作這個物件,也就是面對錯誤並進行改正的機會。
學習Java的人都知道的,例外處理的語法無非就是try..catch...finally,然而其實最重要的,並不在於語法,而在於例外的繼承架構:
Throwable
Error(嚴重的系統錯誤)
LinkageError
ThreadDeath
VirtualMachineError
....
Exception
ClassNotFoundException
CloneNotSupportedException
IllegalAccessException
IOException
FileNotFoundException
....
RuntimeException(執行時期例外)
ArithmeticException
ArrayStoreException
ClassCastException
....
Error(嚴重的系統錯誤)
LinkageError
ThreadDeath
VirtualMachineError
....
Exception
ClassNotFoundException
CloneNotSupportedException
IllegalAccessException
IOException
FileNotFoundException
....
RuntimeException(執行時期例外)
ArithmeticException
ArrayStoreException
ClassCastException
....
實際上所有錯誤發生時,包含錯誤資訊的物件,都是一種(is a)Throwable 的物件,Throwable定義了錯誤訊息的取得、堆疊追蹤(Stack Trace)等方法。在其下有兩個子類別:Error與Exception。
Error的相關子類別代表嚴 重的系統錯誤,例如硬體錯誤、JVM錯誤或記憶體不足等問題,雖然基本上,也可以使用try...catch來處理Error物件,但並不建議,當發生嚴 重系統錯誤時,都是Java應用程式所無力回復的,舉個例子來說,如果JVM所需記憶體不足,你如何直接要求作業系統給予JVM更多記憶體呢?所以 Error物件丟出時,基本上不用處理,任由其傳播至JVM為止,或者是最多留下日誌(log)訊息。
如果一個Throwable物件沒有任何處理,最後傳播至JVM時, JVM只有一種處理方式,顯示堆疊追蹤後直接中斷程式。
Exception下的子類別代表Java應用程式「可能」處理的狀 況,你可以使用try...catch語法嘗試將應用程式回復至可執行的狀態。
在Java程式碼中,如果某個操作會丟出Exception下的子物件,但非屬於RuntimeException的子物件,則你必須明確使用 try...catch語法加以處理,或者至少在方法用throws宣告這個方法會丟出例外,否則的話,編譯器不會讓程式 碼通過編譯,例如:
import
java.io.*;
public class Main {
public static void main(String[] args) {
Reader reader = new FileReader(args[1]);
....
}
}
public class Main {
public static void main(String[] args) {
Reader reader = new FileReader(args[1]);
....
}
}
如果你直接編譯這個程式,則編譯器會出現以下的編譯錯誤訊息:
Main.java:4: unreported exception
java.io.FileNotFoundException; must be caught or declared to be thrown
Reader reader = new FileReader(args[1]);
Reader reader = new FileReader(args[1]);
這是由於FileReader建構方法上,宣告會丟出FileNotFoundException:
public FileReader(String fileName) throws
FileNotFoundException
FileNotFoundException 是IOException的子類別,也就是Exception的子類別,編譯器發現你使用FileReader建構方法時,沒有明確使用try.. catch加以處理,也沒有使用throws在main上宣告丟出,因此不讓你通過編譯。如果想要通過編譯,一個方式是:
import java.io.*;
public class Main {
public static void main(String[] args) {
try {
Reader reader = new FileReader(args[1]);
...
}
catch(FileNotFoundException ex) {
ex.printStackTrace();
}
...
}
}
public class Main {
public static void main(String[] args) {
try {
Reader reader = new FileReader(args[1]);
...
}
catch(FileNotFoundException ex) {
ex.printStackTrace();
}
...
}
}
另一個方法是:
import
java.io.*;
public class Main {
public static void main(String[] args) throws FileNotFoundException {
Reader reader = new FileReader(args[1]);
....
}
}
public class Main {
public static void main(String[] args) throws FileNotFoundException {
Reader reader = new FileReader(args[1]);
....
}
}
採用哪個方式,完全看你的需求而定。像這種Exception下的子物件,但非屬於RuntimeException的子 物件,有個名稱叫作受檢例外(Checked Exception), 受誰檢查?受編譯器檢查。受檢例外存在之目的,在於程式編寫者認定,你進行某個操作時,出錯的機會太高,因此要編譯器來協助(或提醒)你明確加以處理,你無權選擇要不要處理。
屬 於RuntimeException衍生出來的類別,則是由JVM在執行時期會自動丟出的例外,情況通常是你事先無法預測錯誤是否會發生,例如透過參考至 null的變數來試圖操作,會丟出NullPointerExceptioon,或者是使用者輸 入是否正確,這種並不是事先可以得知使用者如何輸入的。例外時期例外,不需要特別使用try-catch或是在函式上使用throws宣告也 可以通過編譯,又稱為非受檢例外(Unchecked Exception)。 例如您在使用陣列時,並不一定要處理ArrayIndexOutOfBoundsException例外也可以通過編譯,你有權選擇要不要處理,或是丟出去給別的呼叫者,甚至最後傳播至JVM。
瞭 解例外處理的繼承架構是必須的,除了解Error與Exception的區別,以及Exception、RuntimeException的分別之外,在 使用try..catch捕捉例外物件時,如果父類別例外物件撰寫在子類別例外物件之前被捕捉,則catch子類別例外物件的區塊將永遠不會被執行,事實 上編譯器也會幫您檢查這個錯誤。例如:
import
java.io.*;
public class Main {
public static void main(String[] args) {
try {
throw new ArithmeticException("例外測試");
}
catch(Exception e) {
System.out.println(e.toString());
}
catch(ArithmeticException e) {
System.out.println(e.toString());
}
}
}
public class Main {
public static void main(String[] args) {
try {
throw new ArithmeticException("例外測試");
}
catch(Exception e) {
System.out.println(e.toString());
}
catch(ArithmeticException e) {
System.out.println(e.toString());
}
}
}
這個程式若在編譯時將會產生以下的錯誤訊息:
Main.java:11: exception
java.lang.ArithmeticException has already been caught
catch(ArithmeticException e) {
^
catch(ArithmeticException e) {
^
要完成這個程式的編譯,你必須更改例外物件捕捉的順序,例如:
import
java.io.*;
public class Main {
public static void main(String[] args) {
try {
throw new ArithmeticException("例外測試");
}
catch(ArithmeticException e) {
System.out.println(e.toString());
}
catch(Exception e) {
System.out.println(e.toString());
}
}
}
public class Main {
public static void main(String[] args) {
try {
throw new ArithmeticException("例外測試");
}
catch(ArithmeticException e) {
System.out.println(e.toString());
}
catch(Exception e) {
System.out.println(e.toString());
}
}
}
在撰寫程式時,也可以如上將Exception例外物件的捕捉撰寫在最後,以便捕捉到所有尚未考慮到的例外,並進一步改進程 式。