1. 程式人生 > 其它 >【java快速入門-異常處理篇】- 丟擲異常

【java快速入門-異常處理篇】- 丟擲異常

技術標籤:java從入門到上升java異常處理丟擲異常java

異常的傳播

根據上一次部落格可知,當一個方法丟擲了異常,如果在本方法中沒有捕獲異常,那麼異常就會被拋到上層呼叫方法,直到遇到某個try.....catch被捕獲為止:

例如:

public class Main {
    public static void main(String[] args) {
        try {
            process1();
        } catch (Exception e) {
            e.printStackTrace();//追蹤錯誤資訊
        }
    }

    static void process1() {
        process2();
    }

    static void process2() {
        Integer.parseInt(null); // 會丟擲NumberFormatException
    }
}

輸出:

printStackTrace()對於除錯錯誤非常有用,上述資訊表示:NumberFormatException是在java.lang.Integer.parseInt方法中被丟擲的,從下往上看,呼叫層次依次是:

  1. main()呼叫process1()
  2. process1()呼叫process2()
  3. process2()呼叫Integer.parseInt(String)
  4. Integer.parseInt(String)呼叫Integer.parseInt(String, int)

然後通過給出的資訊,直接定位到pareseInt的原始碼,可以通過檢視原始碼的方式,找到錯誤的根本原因。

public static int parseInt(String s, int radix) throws NumberFormatException {
    if (s == null) {
        throw new NumberFormatException("null");
    }
    ...
}

丟擲異常

當發生錯誤時,例如,使用者輸入了非法的字元,我們就可以丟擲異常。參考上面的Integer.parseInt()方法,丟擲異常一共分為兩步

  1. 建立某個Exception的例項
  2. throw語句丟擲

例如:

void process2(String s) {
    if (s==null) {
        NullPointerException e = new NullPointerException();
        throw e;
    }
}

或者將他們合併

void process2(String s) {
    if (s==null) {
        throw new NullPointerException();
    }
}

異常型別的轉換:捕獲異常之後,在catch中丟擲新的異常,例如

public class Main {
    public static void main(String[] args) {
        try {
            process1();
        } catch (Exception e) {//在main()中捕獲IllegalArgumentException
            e.printStackTrace();
        }
    }

    static void process1() {
        try {
            process2();
        } catch (NullPointerException e) {
            throw new IllegalArgumentException();//當process2()丟擲NullPointerException後,被process1()捕獲,然後丟擲IllegalArgumentException()。
        }
    }

    static void process2() {
        throw new NullPointerException();
    }
}

輸出

這樣輸出的資訊,無法定位到原始資訊。為了能追蹤到完整的異常棧,在構造異常的時候,把原始的Exception例項傳進去,新的Exception就可以持有原始Exception資訊。對上述程式碼改進如下:

public class Main {
    public static void main(String[] args) {
        try {
            process1();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static void process1() {
        try {
            process2();
        } catch (NullPointerException e) {
            throw new IllegalArgumentException(e);//在構造異常的時候,把原始的Exception例項傳進去
        }
    }

    static void process2() {
        throw new NullPointerException();
    }
}

輸出

注意到Caused by: Xxx,說明捕獲的IllegalArgumentException並不是造成問題的根源,根源在於NullPointerException,是在Main.process2()方法丟擲的。在程式碼中獲取原始異常可以使用Throwable.getCause()方法。如果返回null,說明已經是“根異常”了。

前面提到finally在丟擲異常同樣也是適用的,catch中丟擲異常,不會影響finally的執行。JVM會先執行finally,然後丟擲異常。

異常遮蔽(瞭解)

如果在執行finally語句時丟擲異常

public class Main {
    public static void main(String[] args) {
        try {
            Integer.parseInt("abc");
        } catch (Exception e) {
            System.out.println("process catched");
            throw new RuntimeException(e);
        } finally {
            System.out.println("process finally");
            throw new IllegalArgumentException();
        }
    }
}

這說明finally丟擲異常後,原來在catch中準備丟擲的異常就“消失”了,因為只能丟擲一個異常。沒有被丟擲的異常稱為“被遮蔽”的異常(Suppressed Exception)。

在極少數的情況下,我們需要獲知所有的異常。所以就是需要儲存所有的異常資訊,方法是先用origin變數儲存原始異常,然後呼叫Throwable.addSuppressed(),把原始異常新增進來,最後在finally丟擲:

//異常遮蔽中獲取所有的異常
public class Main {
    public static void main(String[] args) throws Exception {
        Exception origin = null;
        try {
            System.out.println(Integer.parseInt("abc"));
        } catch (Exception e) {
            origin = e;//用origin變數儲存原始異常
            throw e;
        } finally {
            Exception e = new IllegalArgumentException();//丟擲一個新的異常
            if (origin != null) {
                e.addSuppressed(origin);//Throwable.addSuppressed(),把原始異常新增進來
            }
            throw e;
        }
    }
}

catchfinally都丟擲了異常時,雖然catch的異常被遮蔽了,但是,finally丟擲的異常仍然包含了它:

通過Throwable.getSuppressed()可以獲取所有的Suppressed Exception

絕大多數情況下,在finally中不要丟擲異常。因此,我們通常不需要關心Suppressed Exception

小結

呼叫printStackTrace()可以列印異常的傳播棧,對於除錯非常有用;

捕獲異常並再次丟擲新的異常時,應該持有原始異常資訊;

通常不要在finally中丟擲異常。如果在finally中丟擲異常,應該原始異常加入到原有異常中。呼叫方可通過Throwable.getSuppressed()獲取所有新增的Suppressed Exception