簡介

所謂例外(Exception),指的是程式發生不正常的錯誤,而導致無法繼續執行的情形。例外處理(Exception Handling)顧名思義,就是當例外發生時的處理機制。

C語言裡並沒有例外處理的機制,使用函數庫時,可能會發生無法處理的狀況,此時必須由呼叫者小心檢查傳回值才行。如果不檢查,就會發生無法預期的結果

#include <stdio.h>
main() {
    int data;
    FILE* f = fopen("sample.txt", "r"); // sample.txt may not exist, fopen returns NULL
    fscanf(f, "%d", &data); // may result segmentation fault;
}

上述例子裡,如果檔案sample.txt不存在,則無法開啟該檔案,因此fopen傳回NULL。如果不檢查傳回值,則會產生segmentation fault。比較好的寫法是:

#include <stdio.h>
main() {
    int data;
    FILE* f = fopen("sample.txt", "r"); // sample.txt may not exist, fopen returns NULL
    if (f == NULL) {
        printf("Can't open file sample.txt. Please check if it exists and you have privilege to access.\n");
        return;
    }
    fscanf(f, "%d", &data); // may result segmentation fault;
}

又例如sqrt()可以用來求平方根,但如果我們給他負數,sqrt該如何處理?根據手冊,sqrt遇到負數參數,會傳回NaN(Not a Number,是浮點數裡的一個特別數值)。使用sqrt的函數必須特別檢查該值,否則計算出來的東西都變成了NaN。又例如除法運算時分母是0的情況,嚴格說起來也是一種錯誤,某些系統也會產生floating exception (core dumped)。

沒有提供例外處理機制的語言,程式的正確性必須靠極端小心的設計者才行。為了減少程式錯誤的機會,讓軟體很強固(robust),Java提供了例外處理的機制。

相關語法

在Java裡,Exception是一個Class。Exception extends Throwable, Throwable extends Object。Exception,Throwable這兩個類別均定義於java.lang這個package內。設計者也可以自訂自己的Exception類別。相關的Exception語法如下:

try {} catch{}類似像if then else if的結構。當try {}裡面某一行指令產生Exception時,try區塊會立刻中斷執行,然後到第一個catch判斷抓到的Exception是否instanceof TypeOneException,如果是則執行該catch區塊;如果不是,則進一步比較instanceof TypeTwoException。也就是說雖然可以寫很多個catch區塊,但執行時最多只有一塊會執行到。離開try或catch區塊以前,如果有finally區塊,則finally區塊一定會被執行到。一般來說finally區塊裡面的程式碼大多用來作資源回收,或清理資料結構的工作,以確保不論有無發生狀況,程式都能繼續正常執行。
try {
    System.out.println("Opening FileInputStream");
    FileInputStream f = new FileInputStream("abc.txt"); // assume this operation generate FileNotFoundException
    System.out.println("File Opened");
} catch (FileNotFoundException e1) {
    System.out.println("FileNotFoundException caught");
} catch (Exception e2) {
    System.out.println("Exception caught");
} finally {
    System.out.println("Execute finally block");
}

上述的範例會印出

Opening FileInputStream
FileNotFoundException caught
Execute finally block

如果把上面例子稍微改一下:

try {
    System.out.println("Opening FileInputStream");
    FileInputStream f = new FileInputStream("abc.txt"); // assume this operation generate FileNotFoundException
    System.out.println("File Opened");
} catch (Exception e2) {
    System.out.println("Exception caught");
} catch (FileNotFoundException e1) {
    System.out.println("FileNotFoundException caught");
} finally {
    System.out.println("Execute finally block");
}

則聰明一點的Compiler會抱怨FileNotFoundException的區塊unreachable(執行不到)。這是因為FileNotFoundException是Exception的子類別,因此如果產生的例外是instanceof Exception,則FileNotFoundException就不會執行了;若產生的例外不是instanceof Exception,那就更不會執行到FileNotFoundException區塊了。

是否所有的Exception都要處理?

原則上是的。只要用到的method有宣告throws SomeException,則呼叫該method的地方,就要使用try {} catch(SomeException)的語法。當然像是try {} catch(SuperClassOfSomeException)的用法也行。唯一的例外是java.lang.RuntimeException及其子類別可以不必處理。

哪些屬於RuntimeException呢?像是ArrayIndexOutOfBoundException就是其中之一,它發生在陣列索引不合法的情況下:

public class Test {
    public static void main(String[] argv) {
        int[] x = new int[10];
        x[100] = 0; // will generate ArrayIndexOutOfBoundException
    }
}

exception產生時,JVM會由堆疊追蹤此錯誤點的呼叫資訊,並一一向外檢查,直到有try catch區塊攔截此exception為止。在上述的例子中,沒有任何try catch的宣告,則JVM會終止該執行緒

類別Exception的相關方法

抓到例外後,可透過該例外物件,得到有趣的資訊

Error

前面提到Exception是Throwable的子類別。另一個Throwable的子類別是java.lang.Error。所謂Error指的是嚴重的錯誤情況。當Error產生時,其行為和Exceptio類似,但是try catch區塊沒有辦法攔下它們,最後會由JVM來處理Error,並中斷執行緒的執行。像OutOfMemoryError,StackOverflowError都是Error的子類別。

範例

用Link List實作Stack

public class Stack {
    private Node head;
    private int size;
    class Node {
        Object data;
        Node next;
    }
    public void push(Object s) {
        Node tmp = new Node();
        tmp.next = head;
        tmp.data = s;
        size++;
        head = tmp;
    }
    public Object pop() throws Exception {
        if (head == null) {
            throw new Exception();
        }
        Object tmp = head.data;
        head = head.next;
        size--;
        return tmp;
    }
}
public class Example {
    public static void main(String[] argv) {
        Stack s1 = new Stack();
        Stack s2 = new Stack();
        s1.push("abc");
        s1.push("def");
        s2.push("123");
        s2.push("456");
        try {
            s1.pop();
        } catch(Exception e) {}
    }
}
public class Example2 {
    public static void main(String[] argv) throws Exception {
        Stack s1 = new Stack();
        Stack s2 = new Stack();
        s1.push("abc");
        s1.push("def");
        s2.push("123");
        s2.push("456");
        s1.pop();
    }
}

用Link List實作Queue

public class Queue {
    private Node head, tail;
    private int size;
    class Node {
        Object data;
        Node next;
    }
    public void put(Object s) {
        Node tmp = new Node();
        tmp.data = s;
        if (tail != null) {
            tail.next = tmp;
        } else {
            head = tmp;
        }
        tail = tmp;
        size++;
    }
    public Object get() throws Exception {
        if (head == null) {
            throw new Exception();
        }
        Object tmp = head.data;
        head = head.next;
        if (head == null) {
            tail = null;
        }
        size--;
        return tmp;
    }
}