1. 程式人生 > >Java中的異常體系

Java中的異常體系

吹逼兩小時,程式碼五分鐘。平常寫程式碼中異常無處不在,早就練就了我們try{...} actch(){...}finally{...}一把梭的深厚功力。畢竟它看起來真的很簡單,我們用著也蠻順心,但是你真的瞭解它嗎,真的不會出錯嗎?

Java異常體系

下面是Java中的異常繼承圖:

Throwable是整個異常體系中的頂級父類,它擁有兩個子類,分別是ErrorException

Error是由機器底層丟擲的錯誤,我們無法處理的,比如OutOfMemoryError,當遇到這類錯誤時,JVM會直接終止程序,應用終止。此類異常我們不要去捕獲,因為捕獲了也處理不了。

Exception是程式可以處理的錯誤,主要分為執行時異常,和非執行時異常。或者叫做受檢異常和非受檢異常。

執行時異常都是RuntimeException及其子類,比如,NullPointExceptionArrayIndexOutOfBoundsException等等,這類的異常屬於非受檢異常(UnChecked),我們可以對其捕獲處理,也可以不處理,而我們一般也不會做處理的,因為這類錯誤通常是我們邏輯錯誤所導致的,我們應該儘量避免此類Bug

而非執行時異常,比如IOExceptionEOFException等等所有非RuntimeException及其子類,都是非執行時異常,也就是受檢異常(Checked

),當方法丟擲了這類異常,則呼叫者必須在呼叫該方法時對其進行處理,否則將無法編譯。

異常處理

那麼我們該怎麼去處理異常呢,這時候自然而然會想到try-catch-finally,我們來下面的程式碼:

public static void main(String[] args) {
        System.out.println(test1());
    }

    public static boolean test1() {
        boolean flag = true;
        try {
            flag = test2();
        }
catch (Exception e) { System.out.println("[test1] catch exception result=" + flag); flag = false; throw e; } finally { System.out.println("[test1] finally result=" + flag); return flag; } } public static boolean test2() throws Exception { boolean flag = true; try { // 分別呼叫test3()和test4() flag = test4(); if (!flag) { return false; } System.out.println("[test2] result=" + flag); return flag; } catch (Exception e) { System.out.println("[test2] catch exception result=" + flag); flag = false; throw e; } finally { System.out.println("[test2] finally result=" + flag); return flag; } } public static boolean test3() throws Exception { boolean flag = true; try { System.out.println("[test3] result=" + flag); return true; } catch (Exception e) { System.out.println("[test3] catch exception result=" + flag); flag = false; throw e; } finally { System.out.println("[test3] finally result=" + flag); return flag; } } public static boolean test4() throws Exception { boolean flag = true; try { int a = 2 / 0; System.out.println("[test4] result=" + flag); return true; } catch (Exception e) { System.out.println("[test4] catch exception result=" + flag); flag = false; throw e; } finally { System.out.println("[test4] finally result=" + flag); return flag; } }

當其中test2()方法分別呼叫test3()teset4(),列印順序會是怎麼樣的呢,不如先思考一下。

呼叫test3()時,列印結果:

[test3] result=true
[test3] finally result=true
[test2] result=true
[test2] finally result=true
[test1] finally result=true
true

呼叫test4()時,列印結果:

[test4] catch exception result=true
[test4] finally result=false
[test2] finally result=false
[test1] finally result=false
false

不知道這段程式碼的實際執行結果和你所想的有沒有出入,眾所周知的是,當try{···}塊中的程式碼執行未發生異常,賊執行finally{...}塊中的程式碼,如果執行出現異常,則會先執行catch(){...}中的程式碼,再執行finally{...}中的程式碼。但是當這些程式碼塊中出現returnthrow則會發生變化。

當try{···}塊或者catch(){…}塊中有return、throw時,在return或者throw執行前回優先執行finally{…}程式碼塊中的內容

特別提醒,finally中禁止使用return,這裡使用,只是為了演示用。

所以當finally中有throw或者return時,它會覆蓋try塊中和catch塊中的throw以及return,從而出現異常遮蔽現象,比如下面這段程式碼:

public static void main(String[] args) {
        try {
            int i = 1 / 0;
        } catch (Exception e) {
            throw new RuntimeException("catch");
        } finally {
            throw new RuntimeException("finally");
        }
    }

它將打印出:

Exception in thread "main" java.lang.RuntimeException: finally
	at top.felixu.exception.ExceptionDemo1.main(ExceptionDemo1.java:14)

catch塊中的異常就被覆蓋了,這種現象還是有可能出現在try-catch-finally捕獲處理流相關操作中,在finally中手動關閉流出現異常時,但是在JDK7版本以後,try-with-resource可以幫我們自動關閉流了。在JDK7中,所有的IO類都實現了AutoCloseable介面,並且需要實現其中的close()函式,資源釋放過程需要在該函式中完成。那麼,編譯器在編譯時,會自動新增finally程式碼塊,並將close()函式中的資源釋放程式碼加入finally程式碼塊中,從而提高程式碼可讀性。這裡就不具體介紹了。

異常處理約定

  1. 對於非受檢異常,我們不要去捕獲處理,而是通過測試和review程式碼來規避此類問題。
  2. 異常不要用來做流程控制,條件控制,因為異常處理的效率比分支處理要低。
  3. 對大塊程式碼的try-catch是不負責的行為,我們要區分穩定程式碼已經不穩定程式碼。
  4. 捕獲異常是為了處理異常,如果捕獲了什麼都不處理,不如不捕獲,將其拋給其上層呼叫者。而最上層的呼叫者必須處理,防止使用者看到無法理解的異常資訊。
  5. try塊中有事物程式碼,則catch到異常要手動回滾,或者丟擲異常,讓AOP框架來回滾,否則會吞掉異常,導致異常卻未回滾。
  6. 不能在finally中使用return
  7. 捕獲異常必須與所丟擲的異常匹配,或者所捕獲的異常是丟擲異常的父類。