異常的概念和Java異常體系結構
異常是程序運行過程中出現的錯誤。本文主要講授的是Java語言的異常處理。Java語言的異常處理框架,是Java語言健壯性的一個重要體現。
Thorwable類所有異常和錯誤的超類,有兩個子類Error和Exception,分別表示錯誤和異常。
其中異常類Exception又分為運行時異常(RuntimeException)和非運行時異常,這兩種異常有很大的區別,也稱之為不檢查異常(Unchecked Exception)
和檢查異常(Checked Exception)。
- 運行時異常 ---> UncheckedException
- 非運行時異常--->CheckedException

Error和 Exception
-
Error是程序無法處理的錯誤,比如OutOfMemoryError、ThreadDeath等。這些異常發生時,Java虛擬機(JVM)一般會選擇線程終止。 -
Exception是程序本身可以處理的異常,這種異常分兩大類,運行時異常和非運行時異常。程序中應當盡可能去處理這些異常。
2、運行時異常和非運行時異常
運行時異常都是RuntimeException類及其子類異常,如
- NullPointerException、
- IndexOutOfBoundsException
這些異常是不檢查異常(UncheckedException),程序中可以選擇捕獲處理,也可以不處理。這些異常一般是由程序邏輯錯誤引起的,程序應該從邏輯角度盡可能避免這類異常的發生。編譯是可以通過的。
非運行時異常是除RuntimeException以外的異常,類型上都屬於Exception類及其子類。從程序語法角度講是必須進行處理的異常,如果不處理,程序就不能編譯通過。如IOException、SQLException等以及用戶自定義的Exception異常,一般情況下不自定義檢查異常。
異常的捕獲和處理
Java異常的捕獲和處理是一個不容易把握的事情,如果處理不當,不但會讓程序代碼的可讀性大大降低,而且導致系統性能低下,甚至引發一些難以發現的錯誤。
Java異常處理涉及到五個關鍵字,分別是:try、catch、finally、throw、throws。
try、catch、finally三個語句塊應注意的問題
try、catch、finally三個語句塊均不能單獨使用,三者可以組成 try...catch...finally、try...catch、try...finally三種結構,catch語句可以有一個或多個,finally語句最多一個。
try、catch、finally三個代碼塊中變量的作用域為代碼塊內部,分別獨立而不能相互訪問。如果要在三個塊中都可以訪問,則需要將變量定義到這些塊的外面。
多個catch塊時候,只會匹配其中一個異常類並執行catch塊代碼,而不會再執行別的catch塊,並且匹配catch語句的順序是由上到下。
throw、throws關鍵字
throw關鍵字是用於方法體內部,用來拋出一個Throwable類型的異常。
如果拋出了檢查異常,則還應該在方法頭部聲明方法可能拋出的異常類型。該方法的調用者也必須檢查處理拋出的異常。如果所有方法都層層上拋獲取的異常,最終JVM會進行處理,處理也很簡單,就是打印異常消息和堆棧信息。
如果拋出的是Error或RuntimeException,則該方法的調用者可選擇處理該異常。有關異常的轉譯會在下面說明。
當拋出的是運行時異常,程序可以選擇處理和不處理
public static void main(String[] args) {
try {
FileReader file = new FileReader("d:/entitycode");
} catch (FileNotFoundException e) {
e.printStackTrace();
throw new RuntimeException();
}
}
拋出的是非運行時異常時,程序必須要處理,可以繼續往外拋,或者捕獲
public static void main(String[] args) throws FileNotFoundException {
try {
FileReader file = new FileReader("d:/entitycode");
} catch (FileNotFoundException e) {
e.printStackTrace();
throw new FileNotFoundException();
}
}
throws關鍵字用於方法體外部的方法聲明部分,用來聲明方法可能會拋出某些異常。僅當拋出了檢查異常,該方法的調用者才必須處理或者重新拋出該異常。當方法的調用者無力處理該異常的時候,應該繼續拋出,而不是囫圇吞棗一般在catch塊中打印一下堆棧信息做個勉強處理。下面給出一個簡單例子,
Throwable類中的常用方法
getCause():返回拋出異常的原因。如果 cause 不存在或未知,則返回 null。getMessage():返回異常的消息信息。printStackTrace():對象的堆棧跟蹤輸出至錯誤輸出流,作為字段 System.err 的值。
異常處理的一般原則
能處理就早處理,拋出不去還不能處理的就想法消化掉或者轉換為RuntimeException處理。因為對於一個應用系統來說,拋出大量異常是有問題的,應該從程序開發角度盡可能的控制異常發生的可能。
對於檢查異常,如果不能行之有效的處理,還不如轉換為RuntimeException拋出。這樣也讓上層的代碼有選擇的余地――可處理也可不處理。
對於一個應用系統來說,應該有自己的一套異常處理框架,這樣當異常發生時,也能得到統一的處理風格,將優雅的異常信息反饋給用戶。
Java異常總結
Java將異常區分為Error與Exception,Error是程序無力處理的錯誤,Exception是程序可以處理的錯誤。異常處理是為了程序的健壯性。
異常能處理就處理,不能處理就拋出,最終沒有處理的異常JVM會進行處理。
異常可以傳播,也可以相互轉譯,但應該根據需要選擇合理的異常轉譯的方向。
對於一個應用系統,設計一套良好的異常處理體系很重要。這一點在系統設計的時候就應該考慮到。
Exception一般分為Checked異常和Runtime異常,所有RuntimeException類及其子類的實例被稱為Runtime異常,不屬於該范疇的異常則被稱為CheckedException。
Spring事務回滾與異常
Spring被事務管理的方法,需要拋出非檢查異常,即運行期異常才能進行回滾
對非檢查型類異常可以不用捕獲,而檢查型異常則必須用try語句塊進行處理或者把異常交給上級方法處理總之就是必須寫代碼處理它。所以必須在service捕獲異常,然后再次拋出,這樣事務方才起效。
在spring的事務管理環境下,使用unckeckedException可以極大地簡化異常的處理,只需要在事務層聲明可能拋出的異常(這里的異常可以是自定義的unckecked exception體系),在所有的中間層都只是需要簡單throws即可,不需要捕捉和處理,直接到最高層,比如UI層再進行異常的捕捉和處理
在service類前加上@Transactional,聲明這個service所有方法需要事務管理。每一個業務方法開始時都會打開一個事務。
Spring默認情況下會對運行期例外(RunTimeException)進行事務回滾。這個例外是unchecked如果遇到checked意外就不回滾。
如何改變默認規則:
-
1 讓checked例外也回滾:在整個方法前加上 @Transactional(rollbackFor=Exception.class)
-
2 讓unchecked例外不回滾: @Transactional(notRollbackFor=RunTimeException.class)
-
3 不需要事務管理的(只查詢的)方法:@Transactional(propagation=Propagation.NOT_SUPPORTED)
注意: 如果異常被try{}catch{}了,事務就不回滾了,如果想讓事務回滾必須再往外拋try{}catch{throw Exception}。
一個統一的異常層次結構對於提供服務抽象是必需的。 最重要的就是org.springframework.dao.DataAccessException以及其子類了。 需要強調的是Spring的異常機制重點在於應用編程模型。與SqlException和其他數據存取API不同的是: Spring的異常機制是為了讓開發者使用最少, 最清晰的代碼。DataAccessException和其他底層異常都是非檢查性異常(unchecked exception)。 spring的原則之一就是基層異常就應該是非檢查性異常. 原因如下:
- 基層異常通常來說是不可恢復的。
- 檢查性異常將會降低異常層次結構的價值.如果底層異常是檢查性的, 那么就需要在所有地方添加catch語句進行捕獲。
- try/catch代碼塊冗長混亂,而且不增加多少價值。使用檢查異常理論上很好, 但是實際上好象並不如此。Hibernate3也將從檢查性異常轉為非檢查性異常。
