Java通用異常處理錯誤


​ 發現錯誤最理想的時機在編譯階段,然而編譯期間並不能找出所有的錯誤,余下的問題必須在運行期間解決。這就需要錯誤源能通過某種方式,把適當的信息傳遞給某個接收者——該接收者知道將知道如何正確處理這個問題。

1.1 概念

​ 使用異常所帶來的好處是,它往往能降低錯誤處理代碼的復雜度。如果不使用異常,那么就必須檢查特定的錯誤,並在程序中的許多地方去處理它。而如果使用異常,那就不必在方法調用處進行檢查,因為異常處理機制能保證捕獲到這個錯誤。並且,只需在一個地方處理錯誤,即所謂的異常處理程序中。這種方式不僅節省代碼,而且把“描述在正常執行過程中做什么事”的代碼和“出了問題怎么辦”的代碼相分離。

1.2 基本異常

​ 異常情形(exceptional condition)是指阻止當前方法或作用域繼續執行的問題。把異常情形與普通問題區分很重要,所謂普通問題是指,在當前環境下能得到足夠的信息,總能處理這個錯誤。而對於異常情形,就不能繼續下去了,因為在當前環境下無法獲得必要的信息來解決這個問題。

​ 對於異常情形,你所能做的就是從當前環境跳出,並且把問題提交給上一級環境。這就是拋出異常時所發生的事情。

​ 當拋出異常后,有幾件事情會隨之發生。首先,同Java中其它對象的創建一樣,將使用new在堆上創建異常對象。然后當前的執行路徑被終止了,並且從當前環境中彈出對異常對象的引用。此時,異常處理機制接管程序,並開始尋找一個恰當的地方來繼續執行程序。這個恰當的地方就是異常處理程序,它的任務是將程序從錯誤狀態中恢復,以使程序要么換一種方式運行,要么繼續運行下去。

1.3 捕獲異常

​ 要明白異常是如何被捕獲的,必須首先理解監控區域(guarded region)的概念。它是一段可能產生異常的代碼,並且后面跟着處理這些異常的代碼。try{}包含的語句塊就是監控區域,后面跟着catch{}就是處理這些異常的代碼。

終止與恢復

​ 異常處理理論上有兩種基本模型。Java支持終止模型,在這種終止模型中,將假設錯誤非常關鍵,以至於程序無法返回到異常發生的地方繼續執行。一旦異常被拋出,表明錯誤已無法挽回,不能回來繼續執行。

​ 另一種稱為恢復模型。意思是異常處理程序的工作是修正工作,然后嘗試重新調用出問題的方法,並認為第二次能成功。對於恢復模型,通常希望異常處理之后能繼續執行程序。如果希望Java能實現類似恢復的行為,那么在遇見錯誤時就不能拋出異常,而是調用方法來修正該錯誤。或者,把try塊放在while循環里,這樣就不斷進入try塊,知道得到滿意的結果。

​ 長久以來,盡管使用的操作系統支持恢復模型,但是我們最終還是轉向使用類似“終止模型”的代碼,並且忽略恢復行為。其中的原因是恢復模型導致的耦合:恢復性的處理程序需要了解異常拋出的位置,這勢必要包含依賴於拋出位置的非通用性代碼。這增加了代碼編寫和維護的難度,對於異常可能會從許多地方拋出的大型程序來說,更是如此。

1.4 創建自定義異常

​ 要自己定義異常類,要從已有的異常類繼承,最好選擇意思相近的異常類繼承。

class SimpleException extends Exception{}

public class A{
	public void f() throws SimpleException{
		throw new SimpleException();
	}
	
	public static void main(Stirng[] args){
		A a = news A{};
		try{
			a.f();
		}catch(SimpleException e){
			System.out.println("Caught it!");
		}
	}
}/* Output:
Caught it!
*///:~

​ 編譯器創建了默認構造器,它將自動調用基類的默認構造器。本例不會得到像SimpleException(String)這樣的構造器,這種構造器也不實用,對異常來說,最重要的部分就是類名。當然也可以為異常類定義一個接受字符串參數的構造器。

1.5 Java標准異常

​ Throwable這個類被用來表示任何可以作為異常被拋出的類。Throwable對象可分為兩種類型(指從Throwable繼承而得到的類型):Error用來表示編譯時和系統錯誤;Exception是可以被拋出的基本類型,在Java類庫、用戶方法以及運行時故障中都可能拋出Exception型異常。

​ 異常的基本概念是用名稱代表發生的問題,並且異常的名稱應該可以望文知意。

特例:RuntimeException

​ 像NullPointerException這種屬於運行時異常的類型,它們會自動被Java虛擬機拋出,所以不必在異常說明中把它們列出來。這體現了繼承的優點,它們也被稱為“不受檢異常”。這種異常屬於錯誤,它會被自動捕獲。

1.6 使用finally進行清理

​ 對於一些代碼,可能會希望無論try中的異常有沒有拋出,它們都能得到執行。這通常適用於內存回收之外的情況(因為回收由垃圾回收器完成)。為了達到這個效果,可以在異常處理程序后面加上finally子句。

​ 對於沒有垃圾回收和析構函數自動調用機制的語言來說,finally非常重要,它能使程序員保證:無論try塊里發生什么,內存總能得到釋放。但Java有垃圾回收機制,所以內存釋放不再是問題。而且Java也沒有析構函數可供使用。那么,Java在什么情況下才會用到finally呢?

​ 當要把除內存之外的資源恢復到它們的初始狀態時,就要用到finally子句。這種需要清理的資源包括:已經打開的文件或網絡連接,在屏幕上畫的圖形,甚至可以是外部世界的某個開關。

缺憾:異常丟失

​ Java的異常實現也有瑕疵,異常作為程序出錯的標志,決不應該被忽略,但它還是有可能被輕易地忽略。

一種簡單的丟失異常的方式是從finally子句中返回:

public class ExceptionSilencer{
	public static void main(String[] args){
		try{
			throw new RuntimeException();
		}finally{
			return;
		}
	}
}

​ 如果運行這個程序,就會看到即使異常拋出了,也不會產生任何輸出。

1.7 構造器中的異常處理

​ 有一點很重要,即你要時刻詢問自己“如果異常發生了,所有東西能被正確的清理嗎?”。盡管大多數情況下是安全的,但是涉及到構造器時,問題就出現了。

​ 構造器會把對象設置成安全的初始狀態,但還會有別的動作,比如打開一個文件,這樣的動作只有在對象使用完畢並且用戶調用了特殊的清理方法之后才能清理。如果在構造器內拋出了異常,這些清理行為也許就不能正常工作了。這意味着在編寫構造器時要格外小心。

​ 也許你會以為使用finally就可以解決問題。但問題並非如此簡單,因為finally會每次都執行清理代碼。如果構造器在其執行過程中半途而廢,也許該對象的某些部分還沒有被成功創建,而這些部分在finally子句中卻是要被清理的。

(具體例子請詳細閱讀Java編程思想P272)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM