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


1、 異常的概念和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)。下面將詳細講述這些異常之間的區別與聯繫:


一、Error與Exception


Error是程序沒法處理的錯誤,好比OutOfMemoryError、ThreadDeath等。這些異常發生時,

Java虛擬機(JVM)通常會選擇線程終止。



Exception是程序自己能夠處理的異常,這種異常分兩大類運行時異常和非運行時異常。

程序中應當儘量去處理這些異常。


二、運行時異常和非運行時異常


運行時異常都是RuntimeException類及其子類異常,如NullPointerException、IndexOutOfBoundsException等,

這些異常是不檢查異常,程序中能夠選擇捕獲處理,也能夠不處理。這些異常通常是由程序邏輯錯誤引發的,

程序應該從邏輯角度儘量避免這類異常的發生。


非運行時異常是RuntimeException之外的異常,類型上都屬於Exception類及其子類。

從程序語法角度講是必須進行處理的異常,若是不處理,程序就不能編譯經過。

如IOException、SQLException等以及用戶自定義的Exception異常,通常狀況下不自定義檢查異常。



2、 異常的捕獲和處理


Java異常的捕獲和處理是一個不容易把握的事情,若是處理不當,不但會讓程序代碼的可讀性大大下降,

並且致使系統性能低下,甚至引起一些難以發現的錯誤。



Java異常處理涉及到五個關鍵字,分別是:try、catch、finally、throw、throws。下面將驟一介紹,

經過認識這五個關鍵字,掌握基本異常處理知識。


一、 異常處理的基本語法

在java中,異常處理的完整語法是: 前端

Java代碼
  1. try{
  2. //(嘗試運行的)程序代碼
  3. }catch(異常類型 異常的變量名){
  4. //異常處理代碼
  5. }finally{
  6. //異常發生,方法返回以前,老是要執行的代碼
  7. }
[java]   view plain copy print ?
  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語言的健壯性。


二、 try、catch、finally三個語句塊應注意的問題

第1、try、catch、finally三個語句塊均不能單獨使用,三者能夠組成 try...catch...finally、try...catch、

try...finally三種結構,catch語句能夠有一個或多個,finally語句最多一個。

第2、try、catch、finally三個代碼塊中變量的做用域爲代碼塊內部,分別獨立而不能相互訪問。

若是要在三個塊中均可以訪問,則須要將變量定義到這些塊的外面。

第3、多個catch塊時候,只會匹配其中一個異常類並執行catch塊代碼,而不會再執行別的catch塊,

而且匹配catch語句的順序是由上到下。


三、throw、throws關鍵字

throw關鍵字是用於方法體內部,用來拋出一個Throwable類型的異常。若是拋出了檢查異常,

則還應該在方法頭部聲明方法可能拋出的異常類型。該方法的調用者也必須檢查處理拋出的異常。

若是全部方法都層層上拋獲取的異常,最終JVM會進行處理,處理也很簡單,就是打印異常消息和堆棧信息。

若是拋出的是Error或RuntimeException,則該方法的調用者可選擇處理該異常。有關異常的轉譯會在下面說明。


throws關鍵字用於方法體外部的方法聲明部分,用來聲明方法可能會拋出某些異常。僅當拋出了檢查異常,

該方法的調用者才必須處理或者從新拋出該異常。當方法的調用者無力處理該異常的時候,應該繼續拋出,

而不是囫圇吞棗通常在catch塊中打印一下堆棧信息作個勉強處理。下面給出一個簡單例子,

看看如何使用這兩個關鍵字: java

Java代碼
  1. public static void test3() throws Exception{
  2. //拋出一個檢查異常
  3. throw new Exception("方法test3中的Exception");
  4. }
[java]   view plain copy print ?
  1. public static void test3() throws Exception{
  2. //拋出一個檢查異常
  3. throw new Exception("方法test3中的Exception");
  4. }




四、 Throwable類中的經常使用方法

getCause():返回拋出異常的緣由。若是 cause 不存在或未知,則返回 null。

getMessage():返回異常的消息信息。

printStackTrace():對象的堆棧跟蹤輸出至錯誤輸出流,做爲字段 System.err 的值。




3、 異常處理的通常原則


一、 能處理就早處理,拋出不去還不能處理的就想法消化掉或者轉換爲RuntimeException處理。

由於對於一個應用系統來講,拋出大量異常是有問題的,應該從程序開發角度儘量的控制異常發生的可能。

二、 對於檢查異常,若是不能行之有效的處理,還不如轉換爲RuntimeException拋出。

這樣也讓上層的代碼有選擇的餘地――可處理也可不處理。

三、 對於一個應用系統來講,應該有本身的一套異常處理框架,這樣當異常發生時,也能獲得統一的處理風格,

將優雅的異常信息反饋給用戶。


4、 異常的轉譯與異常鏈


一、異常轉譯的原理



所謂的異常轉譯就是將一種異常轉換另外一種新的異常,也許這種新的異常更能準確表達程序發生異常。

在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. }
[java]   view plain copy print ?
  1. public class DAOException extends RuntimeException {
  2. /(省略了部分代碼)
  3. public DAOException(String message, Throwable cause) {
  4. super(message, cause);
  5. }




好比有一個SQLException類型的異常對象e,要轉換爲DAOException,能夠這麼寫: web

Java代碼
  1. DAOException daoEx = new DAOException ( "SQL異常", e);
[java]   view plain copy print ?
  1. 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:目的仍是同樣的。把全部的異常和錯誤轉譯爲不檢查異常,

這樣可讓代碼更爲簡潔,還有利於對錯誤和異常信息的統一處理。



一、 異常鏈


異常鏈顧名思義就是將異常發生的緣由一個傳一個串起來,即把底層的異常信息傳給上層,這樣逐層拋出。

Java API文檔中給出了一個簡單的模型: spring

Java代碼
  1. try {
  2. lowLevelOp();
  3. } catch (LowLevelException le) {
  4. throw (HighLevelException)
  5. new HighLevelException().initCause(le);
  6. }
[java]   view plain copy print ?
  1. try {
  2. lowLevelOp();
  3. } catch (LowLevelException le) {
  4. throw (HighLevelException)
  5. new HighLevelException().initCause(le);
  6. }




當程序捕獲到了一個底層異常le,在處理部分選擇了繼續拋出一個更高級別的新異常給此方法的調用者。

這樣異常的緣由就會逐層傳遞。這樣,位於高層的異常遞歸調用getCause()方法,就能夠遍歷各層的異常緣由。

這就是Java異常鏈的原理。異常鏈的實際應用不多,發生異常時候逐層上拋不是個好注意,

上層拿到這些異常又能奈之何?並且異常逐層上拋會消耗大量資源,

由於要保存一個完整的異常鏈信息.



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


對於一個應用系統來講,發生全部異常在用戶看來都是應用系統內部的異常。所以應該設計一套應用系統的異常框架,

以處理系統運行過程當中的全部異常。


基於這種觀點,能夠設計一個應用系統的異常好比叫作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的子類,還有異常轉換的工具,

這些都是很是優秀的設計思想。



6、 Java異常處理總結


回顧全文,總結一下Java異常處理的要點:

一、 異常是程序運行過程過程出現的錯誤,在Java中用類來描述,用對象來表示具體的異常。

Java將其區分爲Error與Exception,Error是程序無力處理的錯誤,Exception是程序能夠處理的錯誤。

異常處理是爲了程序的健壯性。

二、 Java異常類來自於Java API定義和用戶擴展。經過繼承Java API異常類能夠實現異常的轉譯。

三、 異常能處理就處理,不能處理就拋出,最終沒有處理的異常JVM會進行處理。

四、 異常能夠傳播,也能夠相互轉譯,但應該根據須要選擇合理的異常轉譯的方向。

五、 對於一個應用系統,設計一套良好的異常處理體系很重要。這一點在系統設計的時候就應該考慮到。 編程

相關文章
相關標籤/搜索