在講java異常實踐之前,先了解一下什麼是異常。到底什麼才算是異常呢?其實異常可以看做在我們程式設計過程中遇到的一些意外情況,當出現這些意外情況時我們無法繼續程序正常的邏輯處理,此時我們就可以抛出一個異常。
廣義的講,抛出異常分三種不同的情況:
程式設計錯誤導緻的異常 :在這個類别裡,異常的出現是由于代碼的錯誤(譬如nullpointerexception、illegalargumentexception、indexoutofboundsexception )。代碼通常對程式設計錯誤沒有什麼對策,是以它一般是非檢查異常。
用戶端的錯誤導緻的異常 :用戶端代碼試圖違背制定的規則,調用api不支援的資源。如果在異常中顯示有效資訊的話,用戶端可以采取其他的補救方法。例如:解析一個格式不正确的xml文檔時會抛出異常,異常中含有有效的資訊。用戶端可以利用這個有效資訊來采取恢複的步驟。
資源錯誤導緻的異常 :當擷取資源錯誤時引發的異常。例如,系統記憶體不足,或者網絡連接配接失敗。用戶端對于資源錯誤的反應是視情況而定的。用戶端可能一段時間之後重試或者僅僅記錄失敗然後将程式挂起。
java為異常設計了一套異常處理機制,當程式運作過程中發生一些異常情況時,程式不會傳回任何值,而是抛出封裝了錯誤資訊的異常對象,java 語言提供了專門的異常處理機制去處理這些異常。
那麼為什麼程式設計語言要設計異常呢?首先,引入異常之後,我們就可以把錯誤代碼從正常代碼中分離出來進行單獨處理,這樣使代碼變得更加整潔;其次,當出現一些特殊情況時,我們還可以抛出一個檢查異常,告知調用者讓其處理。

從上面異常繼承樹可以看出,是以異常都繼承自<code>throwable</code>,這也意味着所有異常都是可以抛出的。
具體來說,廣義的異常可以分為<code>error</code>和<code>exception</code>兩大類。
error表示運作應用程式中較嚴重問題。大多數錯誤與代碼編寫者執行的操作無關,而表示代碼運作時 jvm(java 虛拟機)出現的問題。例如,java虛拟機運作錯誤<code>virtual machineerror</code>,當 jvm 不再有繼續執行操作所需的記憶體資源時,将出現<code>outofmemoryerror</code>,虛拟機錯誤還有<code>stackoverflowerror 、internalerror、 unknownerror</code>等。這些異常發生時,java虛拟機(jvm)一般會選擇線程終止。經常見到的error還有<code>linkageerror</code>(結合錯誤),具體有 <code>nosuchmethoderror 、illegalaccesserror 、noclassdeffounderror</code>。
是以,對于error我們程式設計中基本是用不到的,也就是說我們在程式設計中可以忽略error錯誤。是以我們通常所說的異常隻的是exception,而exception可分為檢查異常和非檢查異常。
通常我們所說的異常指的都是exception的子類,它們具體可以分為兩大類在java,<code>exception</code>的子類和<code>runtimeexception</code>的子類,它們分别對應着檢查異常和非檢查異常。
檢查異常,繼承自<code>exception</code>類。對于檢查異常,java強制我們必須進行處理。對于抛出檢查異常的api我們有兩種處理方式:
對抛出檢查異常的api程序try catch
繼續把檢查異常往上抛
常見的檢查異常有:
ps:其實error也是一個檢查型的。
非檢查異常,也稱運作時異常<code>runtimeexception</code> ,繼承自<code>runtimeexception</code>,所有非檢查都有個特點,就是代碼不需要處理它們的異常也能通過編譯,是以它們稱作unchecked exception。<code>runtimeexception</code> 本身也是繼承自<code>exception</code>。
常見的非檢查異常有:
其實對于檢查異常存在的必要性一直都很有争議,java是第一個使用檢查異常的主流面向對象語言,而c++和c#都是沒有檢查異常的。是以我們在程式設計中全部使用非檢查異常(runtimeexception的子類)也是可以的。
但是java為什麼又設計了檢查異常呢,其實個人覺得檢查異常的存在還是有必要的。檢查異常可以使api的調用者明确知道api可能抛出的異常資訊并讓他們能夠對這種異常情況做處理。或者是說,檢查異常允許調用者從異常中恢複,而非檢查異常一般是程式設計錯誤,調用者無法對其進行處理。 但是使用檢查異常需要謹慎。因為檢查異常會強制調用者對其進行try catch或者往上層抛,這樣就給調用者造成了不必要的負擔。
那麼到底何時适合使用檢查異常呢?有一個簡單的原則是:
上面提到,對于檢查異常,強制要求開發者必須進行處理,也就是開發者要麼對其進行try catch,要麼往上層抛。如果檢查異常很多,就意味着程式中需要添加很多的異常處理代碼,導緻晦澀的異常處理,并且檢查異常容易破壞接口方法。為了解決檢查異常帶來的缺陷,我們可以利用異常轉譯的方法,将檢查異常轉化為非檢查異常。
異常轉譯就是将一種異常轉換為另一種異常。異常轉譯針對所有繼承 throwable 超類的異常類而言的。對于我們開發者來說,如果遇到檢查異常,而我們又不知道該對其做出如何處理,那麼我們完全可以在catch塊裡将其封裝成一個非檢查異常然後抛出。例如下面這個例子:
<code>getallaccounts()</code>抛出了兩個checked exception。這個方法的調用者就必須處理這兩個異常,盡管它也不知道在<code>getallaccounts()</code>中什麼檔案找不到以及什麼資料庫語句失敗,也不知道該提供什麼檔案系統或者資料庫的事務層邏輯。這樣,異常處理就在方法調用者和方法之間形成了一個不恰當的緊耦合。但是如果我們真正遇到這種情況,我們完全可以這麼做:
需要注意的是,我們在對異常進行轉譯的時候一定要在構造方法中傳入原異常的throwable對象,這樣可以保留原異常棧資訊,而不是把原異常用另一個異常完全替換掉。
當然,我們也可以根據實際情況将一個非檢查異常包裝成一個檢查異常。
從系統最終使用者的角度來看,系統對于使用者來說就是一個黑盒,使用者并不知道系統如何實作及運作,對使用者而言,系統所出現的任何異常或錯誤,都屬于系統運作時異常。是以在設計面向最終使用者服務的api時,應該捕獲api所有可能出現的異常,并把異常情況封裝成與使用者業務相近的提示資訊,使用者可以根據這些提示資訊作出一些處理。
而對于系統開發者而言,更多的是從系統内部邏輯來看異常。有一部分異常需要内部截獲處理即try catch,而另外一部分異常對于異常産生源而言無法進行有效處理,進而需要向外抛出異常以待合适的調用者進行處理。對于開發者而言,需要預見異常,并且需要考慮何時處理異常,何時抛出異常,必要時以某種方式記錄或通知異常。總而言之,開發者需要通過對系統運作時可能出現的異常盡可能地處理以保證系統的正常運作,并對于無法處理的異常以一種合适的方式記錄、通知、呈現以便找到發生異常的原因,進而解決或避免異常。
一般當程式發生異常時,通常異常處理可能需要做一些通用處理,如異常日志記錄、異常通知,重定向到一個統一的錯誤頁面(如 web 應用)等。如果這些通用異常處理放置于 catch 塊中,将導緻大量的重複代碼,進而可能引起日志備援、同一異常的實作多樣化等問題。另外,大量異常處理程式放置于 catch 塊中造成程式的高耦合性。為了解決此類問題,有必要分離出異常處理程式、統一異常處理風格、降低耦合性、增強異常處理子產品的複用程度。通常的異常處理模式包括業務委托模式(business delegate)、前端控制器模式(front controller)、攔截過濾器模式(intercepting filter)、aop 模式、模闆方法模式等。
在web程式設計中,一般對控制層的異常都應該做統一處理,因為控制層向上面對使用者,是以我們要在控制層捕獲service所有可能出現的異常。上面也提到我們在控制層對每個service調用進行try catch顯然會很繁瑣而且也會導緻大量重複代碼,是以在遇到這種情況時我們一定要考慮引入統一異常處理機制,而很多架構也提供了這樣的處理機制,如spring的aop,springmvc的 exceptionhandler、resteasy的exceptionmapper。
異常層次結構應該以一種普遍通用的原則定義。為此,我們可以利用面向對象語言具備多态的性質,隐藏異常的實際實作。對于異常 service 而言,隻需要捕獲最基本的應用程式異常 appexception,異常處理過濾器會自動過濾實際異常類型并找到相應的異常處理器。另外,在方法的 throws 語句中勿需放入大量的檢查異常;對方法調用者也不會出現混亂的 catch 塊,最多可能隻存在一個用于處理基本應用程式異常 appexception(委托給異常 service 處理)。
前面的章節過,應用系統異常可以從使用者和開發者兩個視角去考慮。是以,我們可以把異常劃分為業務操作異常和系統内部運作時異常兩種類型。抛出業務級異常或系統運作時異常的決策,需要與應用系統本身的架構層次相結合,考慮所要處理異常的層次。如圖所示為一個典型的異常層次結構:
其中,bussinessexception 屬于基本業務操作異常,所有業務操作異常都繼承于該類。例如,通常 ui 層或 web 層是由系統最終使用者執行業務操作驅動,是以最好抛出業務類異常。serviceexception 一般屬于中間服務層異常,該層操作引起的異常一般包裝成基本 serviceexception。daoexception 屬于資料通路相關的基本異常。
對于多層系統,每一層都有該層的基本異常。在層與層之間的資訊傳遞與方法調用時候,一旦在某層發生異常,傳遞到上一層的時候,一般包裝成該層異常,直至與使用者最接近的 ui 層,進而轉化成使用者友好的錯誤資訊。