歐陽宏基,葛 萌
(咸陽師范學院信息工程學院,咸陽712000)
Java異常處理機制應用研究?
歐陽宏基,葛 萌
(咸陽師范學院信息工程學院,咸陽712000)
異常處理機制是Java程序設計中的一個重要方面,正確使用異常處理的策略和方法,能夠確保Java程序結構的清晰性、易讀性和健壯性。分析了Java異常的體系結構,闡述了異常分類與處理機制,提出了異常處理的一般原則和一種異常處理框架,并結合實際應用對該框架進行了詳細描述。
Java異常處理機制;異常處理原則;異常處理框架
異常(又稱為例外)是程序編譯或運行時發生的可預料或不可預料的非正常現象,可能會導致程序中斷或錯誤結果[1]。異常是不可避免的,出現了什么樣的異常?由誰來如何處理異常?如何從異常中恢復?這些問題是任何一門編程語言都要解決的。傳統面向過程的程序語言(例如C語言)通常根據程序返回的某個特殊值或標記,并且假定接收者會檢查該返回值或標記,以此來判斷錯誤是否發生[2]。這種處理方式會在程序的許多地方逐一檢查某個特定的錯誤并加以處理,導致正常的業務流程和錯誤處理代碼緊密耦合,不利于代碼的閱讀和維護。Java語言提供了一整套的、高效的、包括異常拋出、捕獲和處理的機制用于識別和處理異常,并且由Java編譯器強制執行,將描述業務邏輯的代碼與處理異常的代碼分離開來,從而使代碼的可讀性、撰寫、調試和維護都大大提高。
任何中斷程序正常流程的因素都被認為是異常,由于Java是純面向對象的,所以把異常當作對象來處理。JDK API中根據訪問不同資源(例如內存、文件、數據庫等)定義了許多具體異常類,同時允許開發人員根據項目需要自行定義異常類用來描述實際異常信息。層次結構如圖1所示。
Throwable是所有異常和錯誤的父類,它主要包含三個方面的內容:①線程創建時執行堆棧的快照。②用以描述異常或錯誤出現位置的消息字符串。③異常或錯誤產生的原因。Throwable有兩個直接子類:Error和Exception,分別表示錯誤和異常。其中異常Exception又包括兩大類:運行時異常(RuntimeException)和非運行時異常。運行時異常又稱為編譯器不檢查的異常(Unchecked Exception),非運行時異常又稱為編譯器檢查的異常(Checked Exception)。下面將詳細描述這些異常之間的區別與聯系:

圖1 Java異常體系結構
2.1 Error與Exception
Error類層次結構描述了Java運行時系統的內部錯誤和資源耗盡錯誤,例如OutOfMemoryError(內存溢出錯誤)、NoClassDefFoundError(類定義找不到錯誤)等。如果這些錯誤發生(一般情況很少發生),Java虛擬機(JVM)不會檢查Error是否被處理,除了通知給用戶并且會盡力使程序安全的終止外,程序本身是無法處理這些錯誤的。Exception分兩大類:運行時異常和非運行時異常。開發人員在代碼中應當盡可能去處理這些異常,從而保證程序正確執行。
2.2 運行時異常和非運行時異常
各種具體的運行時異常都是RuntimeException類及其子類對象,例如ClassCastException(強制類型轉換異常)、IndexOutOfBoundsException(下標越界異常)等。因為這類異常只有在程序運行階段才能體現出來,所以Java編譯器在編譯階段對代碼是否處理了該類型異常不做檢查,編譯能正確通過。該類型異常一般是由程序邏輯錯誤引起的,所以應從邏輯角度盡可能避免這類異常的發生。各種具體的非運行時異常都是RuntimeException以外的異常,直接從Exception繼承而來,例如IOException(輸入輸出異常)、SQLException(數據庫操作異常)等。這類異常在代碼中必須進行處理,否則編譯不會通過,所以又稱為編譯器檢查異常。
異常處理是指當異常發生后,程序能夠轉向相關的異常處理代碼中并執行嘗試性修復處理,然后根據修復處理的結果決定程序走向,使應用程序能夠正常運行、或降級運行或安全地終止應用程序的執行,以提高應用系統的可靠性[3]。Java異常處理機制通過提供5個關鍵字用來完成對異常的拋出、捕獲和處理這三個過程,分別是:try、catch、finally、throw、throws。前三個關鍵字可分別包含獨立的代碼段依次用來拋出異常、匹配并捕獲異常和處理異常;后兩個關鍵字用來將當前方法所產生的異常聲明拋出,將異常的捕獲和處理操作交給當前方法的調用者。其中try、catch和finally這三個關鍵字所包含的代碼段的執行情況如圖2所示:其中左邊表示沒有異常產生時的執行流程,右邊表示異常發生時的執行流程。

圖2 try catch finally代碼段的執行情況
(1)try代碼段:包含在try中的代碼段可能有多條語句會產生異常。但程序的一次執行過程中如果產生異常,只可能是這些異常中的某一個,該異常對象由Java運行時系統生成并拋出,try中產生異常語句之后的語句都不會被執行;如果這次執行過程中沒有產生異常,那么try中所有的語句都會被執行。
(2)catch代碼段:捕獲try中拋出的異常并在其代碼段中做相應處理,catch語句帶一個Throwable類型的參數,表示可捕獲異常的類型。一般情況下catch代碼段的數量由try中所拋出的異常個數決定。當try中代碼產生的異常被拋出后,catch代碼段按照從上到下的順序(如果異常類型有父子關系,那么子異常所在的catch代碼段位于父異常所在catch代碼段的上方)將異常類型與自己參數所指向的異常類型進行匹配,若匹配成功表示異常被捕獲,程序轉而執行當前catch中的代碼,后面所有的catch代碼段都不會被執行;如果匹配不成功,交給下一個catch進行匹配;如果所有catch都不匹配,表示當前方法不具備處理該異常的能力,對于這種情況如果是一個非運行時異常,為了編譯器通過,必須使用throws關鍵字聲明拋出。
(3)finally代碼段:該代碼段不是必須有的,但如果有,一定緊跟在最后一個catch代碼段后面,作為異常處理機制的統一出口。無論try中是否產生異常,finally中的代碼總在當前方法返回之前無條件執行(除非已經執行了要終止程序的System.exit()方法)。
(4)throw關鍵字用來在方法體內部創建Throwable類型的異常對象并將其拋出,如果是非運行時異常,還必須結合throws關鍵字在方法頭部聲明拋出該異常類型,表明當前方法不具備處理該異常的能力,將異常的處理任務延遲到當前方法的調用者。當前方法的調用者必須檢查、處理或者繼續拋出被調用方法拋出的異常。如果所有方法都層層上拋獲取的異常,最終會在main方法中尋找對應的catch代碼段。如果main中也沒有對異常進行捕獲,那么JVM將通過控制臺打印該異常消息和堆棧信息,同時程序也會終止。
(5)throws關鍵字用來在方法頭部聲明方法可能會拋出的某些異常。僅當拋出了非運行時異常,該方法的調用者才必須處理或者重新拋出該異常。如果方法的調用者無法處理該異常,應該繼續拋出而不是在catch中向控制臺打印異常發生時的堆棧信息,雖然這樣處理對程序調試有幫助,但當程序交付給客戶運行后,printStackTrace這樣的代碼就不具備處理異常的意義了。
4.1 盡可能較早處理異常
一般情況下,try-catch語句不會對應用的性能造成很大影響。僅僅當異常發生時,Java虛擬機需要執行額外的操作來定位處理異常的代碼段,從而會對系統性能產生負面影響。如果拋出異常的代碼段和捕獲異常的代碼段在同一方法中,此種情況對性能的影響最小;如果Java虛擬機必須搜索方法調用棧來尋找異常處理的代碼段,對性能的影響就比較大,尤其當異常處理代碼段位于方法調用棧的底部時。因此,不應該使用異常處理機制來控制程序的正常流程,而應該確保僅僅在程序中可能出現異常的地方使用try-catch語句。而且應該使異常處理代碼位于適當的層次,如果當前方法具備處理某種異常的能力,就盡量處理而不是把能處理的異常拋給方法的調用者。
4.2 對異常進行轉譯
異常轉譯就是將一種異常轉換為另一種新的異常,使得新的異常更能準確描述程序產生異常的原因。因為任何形式的異常和錯誤都是Throwable的子類,而且任何一個異常類都包含接收一個Throwable類型參數的構造方法,這樣就為異常轉譯提供了支持。異常轉譯通常有三種情況,如圖3所示。

圖3 異常轉譯
(1)表示將錯誤轉譯為編譯器檢查的異常并繼續拋出。這樣做的目的是為了最大限度避免因錯誤發生而導致的系統掛起。例如SpringWEB框架中,org.springframework.web.servlet.DispatcherServlet的doDispatch()方法中,將捕獲的錯誤轉譯為一個NestedServletException異常。
(2)表示將編譯器檢查的異常轉譯為Runtime-Exception。可能因為當前方法為了更好的描述業務邏輯而不方便對異常進行有效處理,轉譯為RuntimeException交給上層調用者來處理。例如DAO是Java EE中數據持久層常用的一種設計模式,如果采用JDBC來訪問數據庫就必須對SQLException這個受檢查的異常進行處理。為了將這個異常交給上層調用者,將SQLException轉譯為另外一個新的異常-DAOException(開發人員自己定義的),示例代碼如下所示:

e是一個SQLException類型的異常對象。
(3)表示將錯誤轉譯為RuntimeException。這樣做也是為了將錯誤信息交給上層調用者進行統一處理,不過此種情況在實際開發中較少使用。
4.3 在catch中指定具體的異常類型
不要使用catch(Exception ex)這樣的語句來捕獲異常,雖然這樣能編譯通過而且不用仔細分析try代碼段中異類的類型。根據圖1得知所有的異常都直接或間接從java.lang.Exception繼承而來,根據Java上泛型原理,任何一個異常都會和catch(Exception ex)這樣的代碼匹配,導致子類異常對象“喪失”了被拋出時的上下文,那么在catch代碼段中就無法根據不同異常類型做出不同的處理,也就違背了異常處理的初衷。所以catch語句應當盡量指定具體的異常類型。
4.4 嚴格控制try代碼段的容量
不要把大量的語句都放在某個單一的try代碼段中,因為一大段語句中可能會有不同地方拋出不同的異常。應該仔細分析一大塊代碼中哪些行代碼會拋出什么樣的異常,結合實際程序邏輯,將這些代碼拆分到不同的try代碼段中并結合具體的異常分別進行處理。雖然這樣做增加了try的數量,但是減少了try代碼段中的代碼行數,并且使得異常類型容易分析、代碼易于閱讀。例如采用JDBC進行數據庫編程通常都要執行①加載數據庫驅動、②創建Connection對象、③創建PreparedStatemnt或Statement對象執行SQL操作、④釋放連接等資源這4個步驟。由于步驟①可能會產生ClassNotFoundException,步驟②③④可能會產生SQLException,所以應該將①放在一個try代碼段中,②③④放在另外一個try代碼段中分別進行捕獲和處理。
4.5 合理使用finally代碼段
雖然Java語言提供了垃圾回收機制來自動釋放不再被引用的對象空間,但是如果程序中用到了內存以外的資源,例如IO流、Socket網絡連接以及JDBC數據庫訪問之類的操作,即使發生了異常,也需要通知當前操作系統正確釋放分配給JVM的底層資源。因此,必須把釋放資源之類的代碼(基本都是調用相應的close方法)放到finally代碼段中,從而確保無論異常發生與否,釋放資源之類的代碼總是會被執行。如果finally中還有拋出異常的語句,那么就必須通過try-catch代碼段對相應異常進行捕獲并處理,而不能再聲明拋出了。
對于一個應用系統來說,異常信息不但要讓開發人員看到而且也要讓用戶看到。對用戶而言,異常信息要簡單明了、便于理解;對開發人員而言,異常要便于處理。由于目前Java應用的開發都是采用分層原理,每一層都完成特定的功能,在每一層都有可能產生異常,如果在每一層都處理異常會加重程序員的負擔而且影響代碼的易讀性,因此需要選擇一個合適的位置對異常進行集中處理。
由于Java支持自定義異常,所以創建自定義異常-AppRuntimeException繼承自RuntimeException,如圖4所示。根據4.2節原理其他異常都能轉譯為AppRuntimeException。在AppRuntimeException下層存在著各種具體的異常和錯誤,可以將錯誤和非運行時異常都向AppRuntimeException轉譯,這樣做有兩大優點:①最大限度地避免因錯誤發生而導致的系統崩潰;②使程序代碼更加簡潔,有利于錯誤和異常的統一處理。AppRuntimeException異常的下層可根據Java EE分層開發的原理再自行定義不同的子類異常,例如持久化層的AppDAOException異常。由于持久層的實現技術有多種,例如JDBC、Hibernate、TopLink等。這些不同技術都有最“原始”的異常,例如SQLException、HibernateException等,考慮到松散耦合和可移植性,這些具體異常要向AppDAOException轉譯,在轉譯過程中可通過print-StackTrace打印具體異常信息,方便開發人員調試。通過異常的層層上拋,如果是Web項目,可以在Servlet中或者Struts框架的Action中對AppRuntimeException進行處理[8],并調用相應的錯誤頁面將異常信息簡明的顯示給用戶;如果是桌面應用,可以在窗體類(Frame以及子類)中對異常進行集中處理,并調用相應的Dialog對象框將異常信息顯示給用戶。

圖4 異常處理框架
異常處理機制是Java語言進行軟件開發和測試的一個重要方面。闡述了Java的異常體系結構,詳細描述了Java的異常處理機制,包括try、catch、finally、throw和throws關鍵字的用法和意義。總結了一些異常處理時應遵循的原則,并結合實際項目開發需求,提出了一種異常處理框架。綜合運用這些策略和方法,可以使開發人員編寫出更加簡潔、高效的Java代碼。
[1]杜春濤.Java 6基礎教程[M].北京:清華大學出版社,2011:190-192.
[2][美]Bruce Eckel著.Java編程思想(第二版)[M].侯捷,譯.北京:機械工業出版社,2002:382-383.
[3]王新雨,須文波,柴志雷.Java虛擬機中異常機制實時性的研究及實現[J].計算機工程與應用,2008,44(34):84-86.
[4]陳紅躍,張宏軍,陳剛.Java異常處理策略研究[J].計算機技術與發展,2012,22(7):9-12.
[5]趙智.Java異常處理機制使用經驗與技巧[J].百色學院學報,2008,21(3):102-106.
[6]韓瑞峰.Java異常處理機制及應用研究[J].忻州師范學院學報,2012,28(2):25-27.
[7]楊厚群,陳靜.Java異常處理機制的研究[J].計算機科學,2007,34(3):286-289.
[8]劉淑華.J2EE項目中一種新的錯誤處理方法[J].計算機應用與軟件,2013,30(7):143-145.
Application Research of Java Exception Hand ling Mechanism
OUYANG Hong-ji,GE Meng
(Information Engineering College,Xianyang Normal University,Xianyang 712000,China)
The exception handling mechanism is an important aspect in Java programming,the strategies and methods for proper use of exception handling can ensure the clarity,legibility and robustness of Java Program Structure.The architecture of Java exceptions is analyzed,the exception handling mechanism of classification is described,and its general principles and framework are presented.The framework combined with the practical application is described in details.
Java Exception Handling Mechanism;Exception Handling Principles;Exception Handling Framework
10.3969/j.issn.1002-2279.2014.06.019
TP311
:A
:1002-2279(2014)06-0066-04
咸陽師范學院專項科研計劃項目(12XSYK070)
歐陽宏基(1982-),男,陜西寶雞人,講師,碩士研究生,主研方向:軟件工程、Java EE應用。
2014-04-01