概述
當方法內部發生一項錯誤時,該方法會建立一個對象傳遞給運行時系統(runtime system),這個對象被稱為異常對象,包含錯誤的類型、發生位置,程式狀態等一系列資訊。
當一個方法拋出異常時,運行時系統會沿著調用棧(call stack)尋找該異常的處理方式 。
中,調用棧下面的方法調用了上面的方法,層層嵌套,一共四層:
調用第三個方法時拋出了一個異常,運行時系統就會沿著調用棧反向尋找該異常的處理常式,當該異常類型與某個例外處理常式聲明的異常類型一致時,系統就將該異常交給它處理。
如果系統沒能找到合適的例外處理常式,系統將會終止。
異常類型
java提供了兩種處理異常的方式:
(1) 使用try語句捕獲異常並處理;
(2) 使用throws關鍵字列出要拋出的異常類型,代表在本方法內不做處理,但是調用該方法的方法必須處理該異常或者繼續拋出。
並不是所有異常都需要顯式處理(這裡的處理代表在程式內部捕獲或者拋出),比如IOException、SQLException等是必須要處理的,而NullPointerException、ArithmeticException、IndexOutOfBoundsException等可以不作處理。
理解這一點,就要弄清異常的基本分類。
Checked Exception
這類異常是應用程式可以預見並能夠恢複的錯誤,比如,應用程式需要使用者輸入一個檔案名稱,然後程式將對這個檔案進行讀寫操作。假如使用者輸入的檔案名稱不存在,拋出java.io.FileNotFoundException,應用程式應該捕獲這個異常並提醒使用者。類似這種異常就屬於checked exception。
除了Error、RuntimeException以及兩者的子類,所有異常都屬於checked exception。
Error
Error一般來說是應用程式外部引起的異常,應用程式通常不能預見並恢複。比如,程式順利開啟了一個檔案,但是由於硬體或者作業系統故障,不能夠讀取檔案中的內容,程式就會拋出java.io.IOError。
Runtime Exception
runtimeexception一般來說是程式內部引起的異常,應用程式通常能夠預見並恢複。這類異常的出現一般暗示程式存在bug。比如,還是檔案操作的例子,由於邏輯錯誤,傳入的檔案名稱為空白值,程式就會拋出一個NullPointerException。
雖然可以讓程式捕獲runtimeexception,但更合適的做法是剔除引起這類異常的bug。
java的異常類層次圖如下:
異常的捕獲
checked exception必須捕獲,而unchecked exception的捕獲不是必須的。例如:
import java.io.*;import java.util.List;import java.util.ArrayList; public class ListOfNumbers { private List<Integer> list; private static final int SIZE = 10; public ListOfNumbers () { list = new ArrayList<Integer>(SIZE); for (int i = 0; i < SIZE; i++) { list.add(new Integer(i)); } } public void writeList() { try{ // FileWriter的構造方法 throws IOException, checked exception類型,必須捕獲 PrintWriter out = new PrintWriter(new FileWriter("OutFile.txt")); for (int i = 0; i < SIZE; i++) { // get(int)方法throws IndexOutOfBoundsException,RuntimeException的子類,unchecked exception類型,不是必須要捕獲的 out.println("Value at: "+ i + " = " + list.get(i)); } out.close(); }catch(IOException e){ //捕獲IOException ... } }}
unchecked exception在一些特殊的情況下也可以選擇捕獲它,比如上面的程式,現在既要捕獲IOException,也要捕獲IndexOutOfBoundsException,改寫如下:
public void writeList() { try{ // FileWriter的構造方法 throws IOException, checked exception類型,必須捕獲 PrintWriter out = new PrintWriter(new FileWriter("OutFile.txt")); for (int i = 0; i < SIZE; i++) { // get(int)方法throws IndexOutOfBoundsException,RuntimeException的子類,unchecked exception類型,不是必須要捕獲的 out.println("Value at: "+ i + " = " + list.get(i)); } out.close(); }catch(IndexOutOfBoundsException e){ //捕獲IndexOutOfBoundsException ... }catch(IOException e){ //捕獲IOException ... }
從Java SE 7以後,一個catch塊可以捕獲多個異常,上面的捕獲語句可簡寫為:
catch (IndexOutOfBoundsException | IOException ex) { ...}需要注意的是,這種情況下,catch的參數(上例中的“ex”)預設是final的,不能夠在catch塊中對它再次賦值。
無論異常是否發生,try代碼塊退出後,finally代碼塊都會執行,常常用於釋放資源。例如:
public void writeList() { PrintWriter out = null; try{ out = new PrintWriter(new FileWriter("OutFile.txt")); for (int i = 0; i < SIZE; i++) { out.println("Value at: " +i + " = " + list.get(i)); } }catch(IOException e){ ... }finally{ //釋放資源 if(out!=null){ out.close(); } } }
上例中,有三種情況可導致try代碼塊退出:
(1)newFileWriter("OutFile.txt")拋出IOException
(2)list.get(i)拋出IndexOutOfBoundsException
(3)無異常拋出,代碼執行完畢
無論發生了上面的那種情況,運行時系統都會保證finally代碼塊中的程式執行。
需要注意的是,如果在執行try-catch代碼塊的時候JVM退出了,或者執行try-catch代碼塊的線程被中斷或者殺死,finally代碼塊有可能不被執行。
try-with-resources語句
在try中聲明的一個或多個資源,在程式結束後應該關閉,通常我們是在finally代碼塊中完成這項工作。
從java 7開始,提供了一種更為簡潔有效寫法: try-with-resources語句。
使用形式如下:
static String readFirstLineFromFile(String path) throws IOException { try (BufferedReader br = new BufferedReader(new FileReader(path))) { return br.readLine(); }}try-with-resources確保{}內的程式執行完畢後自動關閉資源,所有實現了java.lang.
AutoCloseable介面(java 7新增的介面,java.lang.Closeable的父介面,)的對象都可以當作資源。
throw與throws
捕獲異常的前提是有方法拋出了異常。
throw關鍵字用於在方法體內部,發生錯誤的地方拋出異常。如:
public Object pop() { Object obj; if (size == 0) { //棧為空白,拋出異常 throw new EmptyStackException(); } obj = objectAt(size - 1); setObjectAt(size - 1, null); size--; return obj;}該方法用於從棧中彈出棧頂元素,但是,如果棧為空白就不能進行這項操作,所以就會拋出EmptyStackException異常。
當其他方法調用pop()方法時,就應該考慮到pop()可能會拋出的異常,如果如果pop()拋出的UncheckedException,可以不做額外的處理;如果pop()拋出的是checked Exception則必須進行處理,可以用try-catch捕獲,也可以選擇在本方法內不做捕獲,繼續用throws關鍵字拋出,如:
public void callPop() throws EmptyStackException { ... pop(); //該方法可能會拋出EmptyStackException ...}
實際上,一個異常很多時候是由於另一個cause異常引起的,因為cause 自身也會有 cause,依此類推,就形成了鏈式異常(Chained Exceptions)。例如:
try {
} catch(IOException e) { //捕獲到IOException時,拋出另一個異常
throw new SampleException("Other IOException", e);
}
Throwable有一種造函數可以接受Throwable類型的參數作為引起該異常的cause,Throwable類的initCause(Throwable)、getCause()可以設定cause資訊、擷取cause資訊。