1. 程式人生 > >Java異常體系結構 侵立刪

Java異常體系結構 侵立刪

轉自:http://www.importnew.com/18994.html

在程式設計中,進行異常處理是非常關鍵和重要的一部分。一個程式的異常處理框架的好壞直接影響到整個專案的程式碼質量以及後期維護成本和難度。試想一下,如果一個專案從頭到尾沒有考慮過異常處理,當程式出錯從哪裡尋找出錯的根源?但是如果一個專案異常處理設計地過多,又會嚴重影響到程式碼質量以及程式的效能。因此,如何高效簡潔地設計異常處理是一門藝術,本文下面先講述Java異常機制最基礎的知識,然後給出在進行Java異常處理設計時的幾個建議。

若有不正之處,請多多諒解和指正,不勝感激。

一.什麼是異常

異常的英文單詞是exception,字面翻譯就是“意外、例外”的意思,也就是非正常情況。事實上,異常本質上是程式上的錯誤,包括程式邏輯錯誤和系統錯誤。比如使用空的引用、陣列下標越界、記憶體溢位錯誤等,這些都是意外的情況,背離我們程式本身的意圖。錯誤在我們編寫程式的過程中會經常發生,包括編譯期間和執行期間的錯誤,在編譯期間出現的錯誤有編譯器幫助我們一起修正,然而執行期間的錯誤便不是編譯器力所能及了,並且執行期間的錯誤往往是難以預料的。假若程式在執行期間出現了錯誤,如果置之不理,程式便會終止或直接導致系統崩潰,顯然這不是我們希望看到的結果。因此,如何對執行期間出現的錯誤進行處理和補救呢?Java提供了異常機制來進行處理,通過異常機制來處理程式執行期間出現的錯誤。通過異常機制,我們可以更好地提升程式的健壯性。

在Java中異常被當做物件來處理,根類是java.lang.Throwable類,在Java中定義了很多異常類(如OutOfMemoryError、NullPointerException、IndexOutOfBoundsException等),這些異常類分為兩大類:Error和Exception。

Error是無法處理的異常,比如OutOfMemoryError,一般發生這種異常,JVM會選擇終止程式。因此我們編寫程式時不需要關心這類異常。

Exception,也就是我們經常見到的一些異常情況,比如NullPointerException、IndexOutOfBoundsException,這些異常是我們可以處理的異常。

Exception類的異常包括checked exception和unchecked exception(unchecked exception也稱執行時異常RuntimeException,當然這裡的執行時異常並不是前面我所說的執行期間的異常,只是Java中用執行時異常這個術語來表示,Exception類的異常都是在執行期間發生的)。

unchecked exception(非檢查異常),也稱執行時異常(RuntimeException),比如常見的NullPointerException、IndexOutOfBoundsException。對於執行時異常,java編譯器不要求必須進行異常捕獲處理或者丟擲宣告,由程式設計師自行決定。

checked exception(檢查異常),也稱非執行時異常(執行時異常以外的異常就是非執行時異常),java編譯器強制程式設計師必須進行捕獲處理,比如常見的IOExeption和SQLException。對於非執行時異常如果不進行捕獲或者丟擲宣告處理,編譯都不會通過。

在Java中,異常類的結構層次圖如下圖所示:

在Java中,所有異常類的父類是Throwable類,Error類是error型別異常的父類,Exception類是exception型別異常的父類,RuntimeException類是所有執行時異常的父類,RuntimeException以外的並且繼承Exception的類是非執行時異常。

典型的RuntimeException包括NullPointerException、IndexOutOfBoundsException、IllegalArgumentException等。

典型的非RuntimeException包括IOException、SQLException等。

二.Java中如何處理異常

在Java中如果需要處理異常,必須先對異常進行捕獲,然後再對異常情況進行處理。如何對可能發生異常的程式碼進行異常捕獲和處理呢?使用try和catch關鍵字即可,如下面一段程式碼所示:

1

2

3

4

5

6

7

try {

  File file = new File("d:/a.txt");

  if(!file.exists())

    file.createNewFile();

} catch (IOException e) {

  // TODO: handle exception

}

被try塊包圍的程式碼說明這段程式碼可能會發生異常,一旦發生異常,異常便會被catch捕獲到,然後需要在catch塊中進行異常處理。

這是一種處理異常的方式。在Java中還提供了另一種異常處理方式即丟擲異常,顧名思義,也就是說一旦發生異常,我把這個異常丟擲去,讓呼叫者去進行處理,自己不進行具體的處理,此時需要用到throw和throws關鍵字。

下面看一個示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

public class Main {

    public static void main(String[] args) {

        try {

            createFile();

        } catch (Exception e) {

            // TODO: handle exception

        }

    }

 

    public static void createFile() throws IOException{

        File file = new File("d:/a.txt");

        if(!file.exists())

            file.createNewFile();

    }

}

這段程式碼和上面一段程式碼的區別是,在實際的createFile方法中並沒有捕獲異常,而是用throws關鍵字宣告丟擲異常,即告知這個方法的呼叫者此方法可能會丟擲IOException。那麼在main方法中呼叫createFile方法的時候,採用try…catch塊進行了異常捕獲處理。

當然還可以採用throw關鍵字手動來丟擲異常物件。下面看一個例子:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

public class Main {

    public static void main(String[] args) {

        try {

            int[] data = new int[]{1,2,3};

            System.out.println(getDataByIndex(-1,data));

        } catch (Exception e) {

            System.out.println(e.getMessage());

        }

 

    }

 

    public static int getDataByIndex(int index,int[] data) {

        if(index<0||index>=data.length)

            throw new ArrayIndexOutOfBoundsException("陣列下標越界");

        return data[index];

    }

}

然後在catch塊中進行捕獲。

也就說在Java中進行異常處理的話,對於可能會發生異常的程式碼,可以選擇三種方法來進行異常處理:

1)對程式碼塊用try..catch進行異常捕獲處理;

2)在 該程式碼的方法體外用throws進行丟擲宣告,告知此方法的呼叫者這段程式碼可能會出現這些異常,你需要謹慎處理。此時有兩種情況:

如果宣告丟擲的異常是非執行時異常,此方法的呼叫者必須顯示地用try..catch塊進行捕獲或者繼續向上層丟擲異常。

如果宣告丟擲的異常是執行時異常,此方法的呼叫者可以選擇地進行異常捕獲處理。

3)在程式碼塊用throw手動丟擲一個異常物件,此時也有兩種情況,跟2)中的類似:

如果丟擲的異常物件是非執行時異常,此方法的呼叫者必須顯示地用try..catch塊進行捕獲或者繼續向上層丟擲異常。

如果丟擲的異常物件是執行時異常,此方法的呼叫者可以選擇地進行異常捕獲處理。

(如果最終將異常拋給main方法,則相當於交給jvm自動處理,此時jvm會簡單地列印異常資訊)

三.深刻理解try,catch,finally,throws,throw五個關鍵字

下面我們來看一下異常機制中五個關鍵字的用法以及需要注意的地方。

1.try,catch,finally

try關鍵字用來包圍可能會出現異常的邏輯程式碼,它單獨無法使用,必須配合catch或者finally使用。Java編譯器允許的組合使用形式只有以下三種形式:

try…catch…;       try….finally……;    try….catch…finally…

當然catch塊可以有多個,注意try塊只能有一個,finally塊是可選的(但是最多隻能有一個finally塊)。

三個塊執行的順序為try—>catch—>finally。

當然如果沒有發生異常,則catch塊不會執行。但是finally塊無論在什麼情況下都是會執行的(這點要非常注意,因此部分情況下,都會將釋放資源的操作放在finally塊中進行)。

在有多個catch塊的時候,是按照catch塊的先後順序進行匹配的,一旦異常型別被一個catch塊匹配,則不會與後面的catch塊進行匹配。

在使用try..catch..finally塊的時候,注意千萬不要在finally塊中使用return,因為finally中的return會覆蓋已有的返回值。下面看一個例子:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.IOException;

 

public class Main {

    public static void main(String[] args) {

        String str = new Main().openFile();

        System.out.println(str);

 

    }

 

    public String openFile() {

        try {

            FileInputStream inputStream = new FileInputStream("d:/a.txt");

            int ch = inputStream.read();

            System.out.println("aaa");

            return "step1";

        } catch (FileNotFoundException e) {

            System.out.println("file not found");

            return "step2";

        }catch (IOException e) {

            System.out.println("io exception");

            return "step3";

        }finally{

            System.out.println("finally block");

            //return "finally";

        }

    }

}

這段程式的輸出結果為:

可以看出,在try塊中發生FileNotFoundException之後,就跳到第一個catch塊,列印”file not found”資訊,並將”step2″賦值給返回值,然後執行finally塊,最後將返回值返回。

從這個例子說明,無論try塊或者catch塊中是否包含return語句,都會執行finally塊。

如果將這個程式稍微修改一下,將finally塊中的return語句註釋去掉,執行結果是:

最後打印出的是”finally”,返回值被重新覆蓋了。

因此如果方法有返回值,切忌不要再finally中使用return,這樣會使得程式結構變得混亂。

2.throws和thow關鍵字

1)throws出現在方法的宣告中,表示該方法可能會丟擲的異常,然後交給上層呼叫它的方法程式處理,允許throws後面跟著多個異常型別;

2)一般會用於程式出現某種邏輯時程式設計師主動丟擲某種特定型別的異常。throw只會出現在方法體中,當方法在執行過程中遇到異常情況時,將異常資訊封裝為異常物件,然後throw出去。throw關鍵字的一個非常重要的作用就是 異常型別的轉換(會在後面闡述道)。

throws表示出現異常的一種可能性,並不一定會發生這些異常;throw則是丟擲了異常,執行throw則一定丟擲了某種異常物件。兩者都是消極處理異常的方式(這裡的消極並不是說這種方式不好),只是丟擲或者可能丟擲異常,但是不會由方法去處理異常,真正的處理異常由此方法的上層呼叫處理。

四.在類繼承的時候,方法覆蓋時如何進行異常丟擲宣告

本小節討論子類重寫父類方法的時候,如何確定異常丟擲宣告的型別。下面是三點原則:

1)父類的方法沒有宣告異常,子類在重寫該方法的時候不能宣告異常;

2)如果父類的方法宣告一個異常exception1,則子類在重寫該方法的時候宣告的異常不能是exception1的父類;

3)如果父類的方法宣告的異常型別只有非執行時異常(執行時異常),則子類在重寫該方法的時候宣告的異常也只能有非執行時異常(執行時異常),不能含有執行時異常(非執行時異常)。 

五.異常處理和設計的幾個建議

以下是根據前人總結的一些異常處理的建議:

1.只在必要使用異常的地方才使用異常,不要用異常去控制程式的流程

謹慎地使用異常,異常捕獲的代價非常高昂,異常使用過多會嚴重影響程式的效能。如果在程式中能夠用if語句和Boolean變數來進行邏輯判斷,那麼儘量減少異常的使用,從而避免不必要的異常捕獲和處理。比如下面這段經典的程式:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

public void useExceptionsForFlowControl() { 

  try

  while (true) { 

    increaseCount(); 

    

  } catch (MaximumCountReachedException ex) { 

  

  //Continue execution 

 

public void increaseCount() throws MaximumCountReachedException { 

  if (count >= 5000

    throw new MaximumCountReachedException(); 

}

上邊的useExceptionsForFlowControl()用一個無限迴圈來增加count直到丟擲異常,這種做法並沒有說讓程式碼不易讀,而是使得程式執行效率降低。

2.切忌使用空catch塊

在捕獲了異常之後什麼都不做,相當於忽略了這個異常。千萬不要使用空的catch塊,空的catch塊意味著你在程式中隱藏了錯誤和異常,並且很可能導致程式出現不可控的執行結果。如果你非常肯定捕獲到的異常不會以任何方式對程式造成影響,最好用Log日誌將該異常進行記錄,以便日後方便更新和維護。

3.檢查異常和非檢查異常的選擇

一旦你決定丟擲異常,你就要決定丟擲什麼異常。這裡面的主要問題就是丟擲檢查異常還是非檢查異常。

檢查異常導致了太多的try…catch程式碼,可能有很多檢查異常對開發人員來說是無法合理地進行處理的,比如SQLException,而開發人員卻不得不去進行try…catch,這樣就會導致經常出現這樣一種情況:邏輯程式碼只有很少的幾行,而進行異常捕獲和處理的程式碼卻有很多行。這樣不僅導致邏輯程式碼閱讀起來晦澀難懂,而且降低了程式的效能。

我個人建議儘量避免檢查異常的使用,如果確實該異常情況的出現很普遍,需要提醒呼叫者注意處理的話,就使用檢查異常;否則使用非檢查異常。

因此,在一般情況下,我覺得儘量將檢查異常轉變為非檢查異常交給上層處理。

4.注意catch塊的順序

不要把上層類的異常放在最前面的catch塊。比如下面這段程式碼:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

try {

        FileInputStream inputStream = new FileInputStream("d:/a.txt");

        int ch = inputStream.read();

        System.out.println("aaa");

        return "step1";

    } catch (IOException e) {

        System.out.println("io exception");       

         return "step2";

    }catch (FileNotFoundException e) {

        System.out.println("file not found");         

        return "step3";

    }finally{

        System.out.println("finally block");

        //return "finally";

    }

第二個catch的FileNotFoundException將永遠不會被捕獲到,因為FileNotFoundException是IOException的子類。

5.不要將提供給使用者看的資訊放在異常資訊裡

比如下面這段程式碼:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

public class Main {

    public static void main(String[] args) {

        try {

            String user = null;

            String pwd = null;

            login(user,pwd);

        } catch (Exception e) {

            System.out.println(e.getMessage());

        }

 

    }

 

    public static void login(String user,String pwd) {

        if(user==null||pwd==null)

            throw new NullPointerException("使用者名稱或者密碼為空");

        //...

    }

}

展示給使用者錯誤提示資訊最好不要跟程式混淆一起,比較好的方式是將所有錯誤提示資訊放在一個配置檔案中統一管理。

6.避免多次在日誌資訊中記錄同一個異常

只在異常最開始發生的地方進行日誌資訊記錄。很多情況下異常都是層層向上跑出的,如果在每次向上丟擲的時候,都Log到日誌系統中,則會導致無從查詢異常發生的根源。

7. 異常處理儘量放在高層進行

儘量將異常統一拋給上層呼叫者,由上層呼叫者統一之時如何進行處理。如果在每個出現異常的地方都直接進行處理,會導致程式異常處理流程混亂,不利於後期維護和異常錯誤排查。由上層統一進行處理會使得整個程式的流程清晰易懂。

8. 在finally中釋放資源

如果有使用檔案讀取、網路操作以及資料庫操作等,記得在finally中釋放資源。這樣不僅會使得程式佔用更少的資源,也會避免不必要的由於資源未釋放而發生的異常情況。

 

轉自:https://blog.csdn.net/junlixxu/article/details/6096266

 

一、 異常的概念和Java異常體系結構 

    異常是程式執行過程中出現的錯誤。本文主要講授的是Java語言的異常處理。Java語言的異常處理框架, 
    是Java語言健壯性的一個重要體現。 

    Java把異常當作物件來處理,並定義一個基類java.lang.Throwable作為所有異常的超類。 
    在Java API中已經定義了許多異常類,這些異常類分為兩大類,錯誤Error和異常Exception。 
    Java異常體系結構呈樹狀,其層次結構圖如圖 1所示: 
    
     

    圖 1  Java異常體系結構 

    Thorwable類所有異常和錯誤的超類,有兩個子類Error和Exception,分別表示錯誤和異常。 
    其中異常類Exception又分為執行時異常(RuntimeException)和非執行時異常, 
    這兩種異常有很大的區別,也稱之為不檢查異常(Unchecked Exception) 
    和檢查異常(Checked Exception)。下面將詳細講述這些異常之間的區別與聯絡: 

    1、Error與Exception 

    Error是程式無法處理的錯誤,比如OutOfMemoryError、ThreadDeath等。這些異常發生時, 
    Java虛擬機器(JVM)一般會選擇執行緒終止。 


    Exception是程式本身可以處理的異常,這種異常分兩大類執行時異常和非執行時異常。 
    程式中應當儘可能去處理這些異常。 

    2、執行時異常和非執行時異常 

    執行時異常都是RuntimeException類及其子類異常,如NullPointerException、IndexOutOfBoundsException等, 
    這些異常是不檢查異常,程式中可以選擇捕獲處理,也可以不處理。這些異常一般是由程式邏輯錯誤引起的, 
    程式應該從邏輯角度儘可能避免這類異常的發生。 

    非執行時異常是RuntimeException以外的異常,型別上都屬於Exception類及其子類。 
    從程式語法角度講是必須進行處理的異常,如果不處理,程式就不能編譯通過。 
    如IOException、SQLException等以及使用者自定義的Exception異常,一般情況下不自定義檢查異常。 


二、 異常的捕獲和處理 

    Java異常的捕獲和處理是一個不容易把握的事情,如果處理不當,不但會讓程式程式碼的可讀性大大降低, 
    而且導致系統性能低下,甚至引發一些難以發現的錯誤。 


    Java異常處理涉及到五個關鍵字,分別是:try、catch、finally、throw、throws。下面將驟一介紹, 
    通過認識這五個關鍵字,掌握基本異常處理知識。 

    1、 異常處理的基本語法 
    在java中,異常處理的完整語法是:

 

Java程式碼 

  1.  try{   
  2.   //(嘗試執行的)程式程式碼   
  3. }catch(異常型別 異常的變數名){   
  4.   //異常處理程式碼   
  5. }finally{   
  6.   //異常發生,方法返回之前,總是要執行的程式碼   
  7. }  
 
  1. try{

  2. //(嘗試執行的)程式程式碼

  3. }catch(異常型別 異常的變數名){

  4. //異常處理程式碼

  5. }finally{

  6. //異常發生,方法返回之前,總是要執行的程式碼

  7. }





    以上語法有三個程式碼塊: 
    try語句塊,表示要嘗試執行程式碼,try語句塊中程式碼受異常監控,其中程式碼發生異常時,會丟擲異常物件。 

    catch語句塊會捕獲try程式碼塊中發生的異常並在其程式碼塊中做異常處理,catch語句帶一個Throwable型別的引數, 
    表示可捕獲異常型別。當try中出現異常時,catch會捕獲到發生的異常,並和自己的異常型別匹配, 
    若匹配,則執行catch塊中程式碼,並將catch塊引數指向所拋的異常物件。catch語句可以有多個, 
    用來匹配多箇中的一個異常,一旦匹配上後,就不再嘗試匹配別的catch塊了。 
    通過異常物件可以獲取異常發生時完整的JVM堆疊資訊,以及異常資訊和異常發生的原因等。 

    finally語句塊是緊跟catch語句後的語句塊,這個語句塊總是會在方法返回前執行, 
    而不管是否try語句塊是否發生異常。並且這個語句塊總是在方法返回前執行。 
    目的是給程式一個補救的機會。這樣做也體現了Java語言的健壯性。 

    2、 try、catch、finally三個語句塊應注意的問題 
    第一、try、catch、finally三個語句塊均不能單獨使用,三者可以組成 try...catch...finally、try...catch、 
    try...finally三種結構,catch語句可以有一個或多個,finally語句最多一個。 
    第二、try、catch、finally三個程式碼塊中變數的作用域為程式碼塊內部,分別獨立而不能相互訪問。 
    如果要在三個塊中都可以訪問,則需要將變數定義到這些塊的外面。 
    第三、多個catch塊時候,只會匹配其中一個異常類並執行catch塊程式碼,而不會再執行別的catch塊, 
    並且匹配catch語句的順序是由上到下。 

    3、throw、throws關鍵字 
    throw關鍵字是用於方法體內部,用來丟擲一個Throwable型別的異常。如果丟擲了檢查異常, 
    則還應該在方法頭部宣告方法可能丟擲的異常型別。該方法的呼叫者也必須檢查處理丟擲的異常。 
    如果所有方法都層層上拋獲取的異常,最終JVM會進行處理,處理也很簡單,就是列印異常訊息和堆疊資訊。 
    如果丟擲的是Error或RuntimeException,則該方法的呼叫者可選擇處理該異常。有關異常的轉譯會在下面說明。 

    throws關鍵字用於方法體外部的方法宣告部分,用來宣告方法可能會丟擲某些異常。僅當丟擲了檢查異常, 
    該方法的呼叫者才必須處理或者重新丟擲該異常。當方法的呼叫者無力處理該異常的時候,應該繼續丟擲, 
    而不是囫圇吞棗一般在catch塊中列印一下堆疊資訊做個勉強處理。下面給出一個簡單例子, 
    看看如何使用這兩個關鍵字:

 

Java程式碼 

  1. public static void test3() throws Exception{   
  2.   //丟擲一個檢查異常   
  3.         throw new Exception("方法test3中的Exception");   
  4.     }   
 
  1. public static void test3() throws Exception{

  2. //丟擲一個檢查異常

  3. throw new Exception("方法test3中的Exception");

  4. }



    4、 Throwable類中的常用方法 
    getCause():返回丟擲異常的原因。如果 cause 不存在或未知,則返回 null。 
    getMessage():返回異常的訊息資訊。 
    printStackTrace():物件的堆疊跟蹤輸出至錯誤輸出流,作為欄位 System.err 的值。 



三、 異常處理的一般原則 

    1、 能處理就早處理,丟擲不去還不能處理的就想法消化掉或者轉換為RuntimeException處理。 
    因為對於一個應用系統來說,丟擲大量異常是有問題的,應該從程式開發角度儘可能的控制異常發生的可能。 
    2、 對於檢查異常,如果不能行之有效的處理,還不如轉換為RuntimeException丟擲。 
    這樣也讓上層的程式碼有選擇的餘地――可處理也可不處理。 
    3、 對於一個應用系統來說,應該有自己的一套異常處理框架,這樣當異常發生時,也能得到統一的處理風格, 
    將優雅的異常資訊反饋給使用者。 

四、 異常的轉譯與異常鏈 

    1、異常轉譯的原理 


    所謂的異常轉譯就是將一種異常轉換另一種新的異常,也許這種新的異常更能準確表達程式發生異常。 
    在Java中有個概念就是異常原因,異常原因導致當前丟擲異常的那個異常物件, 
    幾乎所有帶異常原因的異常構造方法都使用Throwable型別做引數,這也就為異常的轉譯提供了直接的支援, 
    因為任何形式的異常和錯誤都是Throwable的子類。比如將SQLException轉換為另外一個新的異常DAOException, 
    可以這麼寫: 

    先自定義一個異常DAOException:

 

Java程式碼 

  1. public class DAOException extends RuntimeException {   
  2. /(省略了部分程式碼)   
  3.   public DAOException(String message, Throwable cause) {   
  4.       super(message, cause);   
  5.   }   
 
  1. public class DAOException extends RuntimeException {

  2. //(省略了部分程式碼)

  3. public DAOException(String message, Throwable cause) {

  4. super(message, cause);

  5. }

  6. }



    比如有一個SQLException型別的異常物件e,要轉換為DAOException,可以這麼寫:

 

Java程式碼 

  1. DAOException daoEx = new DAOException ( "SQL異常", e);   
    DAOException daoEx = new DAOException ( "SQL異常", e); 



    異常轉譯是針對所有繼承Throwable超類的類而言的,從程式設計的語法角度講,其子類之間都可以相互轉換。 
    但是,從合理性和系統設計角度考慮,可將異常分為三類:Error、Exception、RuntimeException,筆者認為, 
    合理的轉譯關係圖應該如圖 2: 

 


    圖 2 異常轉譯 

    為什麼要這麼做呢?筆者認為,異常的處理存在著一套哲學思想:對於一個應用系統來說, 
    系統所發生的任何異常或者錯誤對操作使用者來說都是系統"執行時"異常,都是這個應用系統內部的異常。 
    這也是異常轉譯和應用系統異常框架設計的指導原則。在系統中大量處理非檢查異常的負面影響很多, 
    最重要的一個方面就是程式碼可讀性降低,程式編寫複雜,異常處理的程式碼也很蒼白無力。 
    因此,很有必要將這些檢查異常Exception和錯誤Error轉換為RuntimeException異常, 
    讓程式設計師根據情況來決定是否捕獲和處理所發生的異常。 


    圖中的三條線標識轉換的方向,分三種情況: 

    ①:Error到Exception:將錯誤轉換為異常,並繼續丟擲。例如Spring WEB框架中, 
    將org.springframework.web.servlet.DispatcherServlet的doDispatch()方法中, 
    將捕獲的錯誤轉譯為一個NestedServletException異常。這樣做的目的是為了最大限度挽回因錯誤發生帶來的負面影響。 
    因為一個Error常常是很嚴重的錯誤,可能會引起系統掛起。 

    ②:Exception到RuntimeException:將檢查異常轉換為RuntimeException可以讓程式程式碼變得更優雅, 
    讓開發人員集中經理設計更合理的程式程式碼,反過來也增加了系統發生異常的可能性。 

    ③:Error到RuntimeException:目的還是一樣的。把所有的異常和錯誤轉譯為不檢查異常, 
    這樣可以讓程式碼更為簡潔,還有利於對錯誤和異常資訊的統一處理。 


    1、 異常鏈 

    異常鏈顧名思義就是將異常發生的原因一個傳一個串起來,即把底層的異常資訊傳給上層,這樣逐層丟擲。 
    Java API文件中給出了一個簡單的模型:

 

Java程式碼 

  1. try {   
  2.  lowLevelOp();   
  3. catch (LowLevelException le) {   
  4.   throw (HighLevelException)   
  5.   new HighLevelException().initCause(le);   
  6. }  
 
  1. try {

  2. lowLevelOp();

  3. } catch (LowLevelException le) {

  4. throw (HighLevelException)

  5. new HighLevelException().initCause(le);

  6. }



    當程式捕獲到了一個底層異常le,在處理部分選擇了繼續丟擲一個更高級別的新異常給此方法的呼叫者。 
    這樣異常的原因就會逐層傳遞。這樣,位於高層的異常遞迴呼叫getCause()方法,就可以遍歷各層的異常原因。 
    這就是Java異常鏈的原理。異常鏈的實際應用很少,發生異常時候逐層上拋不是個好注意, 
    上層拿到這些異常又能奈之何?而且異常逐層上拋會消耗大量資源, 
    因為要儲存一個完整的異常鏈資訊. 


五、 設計一個高效合理的異常處理框架 

    對於一個應用系統來說,發生所有異常在使用者看來都是應用系統內部的異常。因此應該設計一套應用系統的異常框架, 
    以處理系統執行過程中的所有異常。 

    基於這種觀點,可以設計一個應用系統的異常比如叫做AppException。並且對使用者來說, 
    這些異常都是執行應用系統執行時發生的,因此AppException應該繼承RuntimeException, 
    這樣系統中所有的其他異常都轉譯為AppException,當異常發生的時候,前端接收到AppExcetpion並做統一的處理。 
    
    畫出異常處理框架如圖 3 : 

 



     圖 3 一個應用系統的異常處理框架 

    在這個設計圖中,AppRuntimeException是系統異常的基類,對外只丟擲這個異常, 
    這個異常可以由前端(客戶端)接收處理,當異常發生時,客戶端的相關元件捕獲並處理這些異常, 
    將"友好"的資訊展示給客戶。 

    在AppRuntimeException下層,有各種各樣的異常和錯誤,最終都轉譯為AppRuntimeException, 
    AppRuntimeException下面還可以設計一些別的子類異常,比如AppDAOException、OtherException等, 
    這些都根據實際需要靈活處理。 
    在往下就是如何將捕獲的原始異常比如SQLException、HibernateException轉換為更高階一點AppDAOException。 


    有關異常框架設計這方面公認比較好的就是Spring,Spring中的所有異常都可以用org.springframework.core.NestedRuntimeException來表示,並且該基類繼承的是RuntimeException。 
    Spring框架很龐大,因此設計了很多NestedRuntimeException的子類,還有異常轉換的工具, 
    這些都是非常優秀的設計思想。 


六、 Java異常處理總結 

    回顧全文,總結一下Java異常處理的要點: 
    1、 異常是程式執行過程過程出現的錯誤,在Java中用類來描述,用物件來表示具體的異常。 
        Java將其區分為Error與Exception,Error是程式無力處理的錯誤,Exception是程式可以處理的錯誤。 
        異常處理是為了程式的健壯性。 
    2、 Java異常類來自於Java API定義和使用者擴充套件。通過繼承Java API異常類可以實現異常的轉譯。 
    3、 異常能處理就處理,不能處理就丟擲,最終沒有處理的異常JVM會進行處理。 
    4、 異常可以傳播,也可以相互轉譯,但應該根據需要選擇合理的異常轉譯的方向。 
    5、 對於一個應用系統,設計一套良好的異常處理體系很重要。這一點在系統設計的時候就應該考慮到。