【java基礎(四十七)】異常(三)捕獲異常、異常鏈

拋出異常的過程十分容易,只要將其拋出就不用理睬了。當然,有些代碼必須捕獲異常,且捕獲異常需要進行周密的計劃。

捕獲異常

如果某個異常發生的時候沒有在任何地方進行捕獲,那程序就會終止執行,並在控制檯上打印出異常信息,其中包括異常的類型和堆棧的內容。

要想捕獲一個異常,必須設置try/catch語句塊。簡單的try語句塊如下:

try {
	code...
	....
} catch (ExceptionType e) {
	handler for this type
}

如果在try語句塊中的任何代碼拋出了一個在catch字句中說明的異常類,那麼:

  • 程序將跳過try語句塊的其餘代碼。
  • 程序將執行catch字句中的處理器代碼

如果在try語句塊中的代碼沒有拋出任何異常,那麼程序將跳過catch字句。

如果方法中的任何代碼拋出了一個在catch字句中沒有聲明的異常類型,那麼這個方法就會立刻退出。

爲了演示捕獲異常的處理過程,我們看一個讀取數據的典型程序代碼:

public void read(String filename) {
	try {
		InputStream in = new FileInputStream(filename);
		int b;
		while ((b = in.read()) != -1)
			...
	} catch (IOException exception) {
		exception.printStrackTrace();
	}
}

需要注意的是,try語句中的大多數代碼都很容易理解:讀取並處理字節,知道遇到文件結束符爲止。正如Java API中看到的那樣,read方法有可能拋出一個IOException異常。在這種情況下,將跳出整個while循環,進入catch字句,並生成一個棧軌跡。對於一個普通的程序來說,這樣處理異常基本上合乎情理。還有其他的選擇嗎?

通常,最好的選擇是什麼也不做,而是將異常傳遞給調用者。如果read方法出現了錯誤,就讓read方法的調用者去操心。如果採用這種處理方式,就必須聲明這個方法可能會拋出一個IOException。

public void read(String filename) throws IOExcepton {
	InputStream in = new FileInputStream(filename);
	int b;
	while ((b = in.read()) != -1)
		...
}

請記住,編譯器嚴格地執行throws說明符。如果調用了一個拋出受查異常的方法,就必須對它進行處理,或者繼續傳遞。

哪種方法更好呢?通常,應該捕獲那些知道如何處理的異常,而將那些不知道怎麼處理的異常繼續進行傳遞。

如果想傳遞一個異常,就必須在方法的首部添加一個throws說明符,以便告知調用者這個方法可能會拋出異常。

這個規則也有一個例外,如果編寫一個覆蓋超類的方法,而這個方法又沒有拋出異常,那麼這個方法就必須捕獲方法代碼中出現的每一個受查異常。不允許在子類的throws說明符中出現超類方法所列出的異常類範圍。

捕獲多個異常

在一個try語句塊中可以捕獲多個異常類型,並對不同的異常做出不同的處理。可以按照下列方式爲每個異常類型使用一個單獨的catch字句:

try {
	code that might throw exceptions
} catch (FileNotFoundExcetpion e) {
	...
} catch (UnknownHostException e) {
	...
} catch (IOException e) {
	...
}

異常對象可能包含與異常本身有關的信息。要想獲得對象的更多信息,可以試着使用

e.getMessage()

得到詳細的錯誤信息,或者使用

e.getClass().getName()

得到異常對象的實際類型。

在Java SE7中,同一個catch字句可以捕獲多個異常類型。如:

try {
	code that might throw exceptions
} catch (FileNotFoundException | UnknownHostException e) {
	...
} catch (IOException e) {
	...
}

只有當捕獲的異常類型彼此之間不存在子類關係時才需要這個特性。

再次拋出異常與異常鏈

在catch子句中可以拋出一個異常,這樣做的目的是改變異常的類型。如果開發了一個供其他程序員使用的子系統,那麼,用於表示子系統故障的異常類型可能會產生多種解釋。ServletException就是這樣一個異常類型的例子。執行servlet的代碼可能不想知道發生錯誤的細節原因,但希望明確地知道servlet是否有問題。如:

try {
	access the database
} catch (SQLException e) {
	throw new ServletException("database error:" + e.getMessage());
}

這裏,ServletException用帶有異常信息文本的構造器來構造。

不過,可以有一種更好的處理方法,並且將原始異常設置爲新異常的“原因”:

try {
	access the database
} catch (SQLException e) {
	Throwable se = new ServletException("database error");
	se.initCause(e);
	throw se;
}

當捕獲到異常時,就可以使用下面這條語句重新得到原始異常:

Throwable e = se.getCause();

強烈建議使用這種包裝技術。這樣可以讓用戶拋出子系統中的高級異常,而不會丟失原始異常的細節。

有時你可能只想記錄一個異常,而將它重新拋出,而不做任何改變:

try {
	access the database
} catch (Exception e) {
	logger.log(level, message, e);
	throw e;
}

在Java SE 7之前,這種方法存在一個問題,假設這個代碼在以下方法中:

public void updateRecord() throws SQLException

Java編譯器查看catch塊中的throw語句,然後查看e的類型,會指出這個方法可以拋出任何Exception而不只是SQLException。

現在這個問題已經有所改進。編譯器會跟蹤到e來自try塊。假設這個try塊中僅有的已檢查異常是SQLException實例,另外,假設e在catch塊中未改變,將外圍方法聲明爲throws SQLException就是合法的。

捐贈

若你感覺讀到這篇文章對你有啓發,能引起你的思考。請不要吝嗇你的錢包,你的任何打賞或者捐贈都是對我莫大的鼓勵。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章