1. 程式人生 > >java異常體系結構

java異常體系結構

一、 異常的概念及分類

異常是指程式執行時出現的錯誤,java語言的異常處理框架是java語言健壯性的一個重要體現。

java把異常當做物件來處理,並定義一個基類java.lang.Throwable作為所有異常的超類。

在javaAPI中已經定義了許多異常類,這些異常類分為兩大類,錯誤(Error)和異常(Exception)。

1、Error和Exception

Throwable是所有異常和錯誤的超類,有兩個子類,分別是Error和Excetion。Exception又分為執行時異常和非執行時異常,用他的兩個子類來表示,程式中應該儘可能的去處理這些異常。

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

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

非執行時異常時除RuntimeException以外的異常,從語法角度講,是必須處理的異常,如果不處理程式編譯不通過,如IOException和SQLException等以及使用者自定義的異常。

二、異常的捕獲和處理

在java中處理異常的完整語法是

try{   
  //(嘗試執行的)程式程式碼   
}catch(異常型別 異常的變數名){   
  //異常處理程式碼   
}finally{  
  //總是要執行的程式碼   
} 
1、try語句塊中表示嘗試執行的程式碼,try語句塊中的程式碼受異常監控,其中程式碼發生異常時,會丟擲異常物件。

catch語句塊會捕獲與自己型別相同的異常型別,並進行異常處理程式碼。

finally是緊跟catch的語句塊,不管try語句塊中的異常是否發生,finally總會執行。

2、使用try、catch、finally時應注意的事情:第一、try,catch,finally均不能單獨使用,3個語句塊只有3中組合形式,try{}catch,try{}finally,try{}catch{}finally{},catch可以有一個或多個,finally語句最多隻有一個。第二,try,catch,finally3個語句塊內部的變數的作用域是塊內部,分別獨立而不能互相訪問,如需在3個塊中都訪問則需要定義在它們外面。第三,多個catch塊的時候只會匹配一個catch,並且匹配的順序是從上到下。

3、throw和throws關鍵字區別

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

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

三、異常的一般處理原則

1、能早處理就早處理,拋不出去還不能處理的就想法消化掉或者轉換為RuntimeException處理;

2、對於檢查異常,如果不能行之有效的處理還不如轉換為RuntimeException丟擲,這樣也讓上層程式碼有選擇的餘地,可以處理也可以不處理。

3、對於一個應用系統來說,應該有自己的一套異常處理框架,這樣當異常發生時,也能得到統一的異常處理風格,將優雅的異常資訊反饋給客戶。

四、異常的轉譯與異常鏈

1、異常轉譯的原理

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

先自定一個DAOException:

public class DAOException extends RuntimeException {   
/(省略了部分程式碼)   
  public DAOException(String message, Throwable cause) {   
      super(message, cause);   
  }
比如有一個SQLException轉換成DAOException,可以這麼寫

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

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

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

圖中的三條線標識轉換的方向,分三種情況: 
①:Error到Exception:將錯誤轉換為異常,並繼續丟擲。例如Spring WEB框架中,將org.springframework.web.servlet.DispatcherServlet的doDispatch()方法中,將捕獲的錯誤轉譯為一個NestedServletException異常。這樣做的目的是為了最大限度挽回因錯誤發生帶來的負面影響。因為一個Error常常是很嚴重的錯誤,可能會引起系統掛起。 
②:Exception到RuntimeException:將檢查異常轉換為RuntimeException可以讓程式程式碼變得更優雅,讓開發人員集中經理設計更合理的程式程式碼,反過來也增加了系統發生異常的可能性。 
③:Error到RuntimeException:目的還是一樣的。把所有的異常和錯誤轉譯為不檢查異常,這樣可以讓程式碼更為簡潔,還有利於對錯誤和異常資訊的統一處理。 
2、異常鏈

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

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

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

 對於一個應用系統來說,發生所有異常在使用者看來都是應用系統內部的異常。因此應該設計一套應用系統的異常框架,以處理系統執行過程中的所有異常。 基於這種觀點,可以設計一個應用系統的異常比如叫做AppException。並且對使用者來說,這些異常都是執行應用系統執行時發生的,因此AppException應該繼承RuntimeException,這樣系統中所有的其他異常都轉譯為AppException,當異常發生的時候,前端接收到AppExcetpion並做統一的處理。 
 畫出異常處理框架如圖 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、 對於一個應用系統,設計一套良好的異常處理體系很重要。這一點在系統設計的時候就應該考慮到。