【java基礎(四十八)】異常(四)finally 、try - with - resources

finally子句

當代碼拋出一個異常時,就會終止方法中剩餘代碼的處理,並退出這個方法的執行。如果方法獲得了一些本地資源,並且只有這個方法自己知道,又如果這些資源在退出方法之前必須被回收,那麼就會產生資源回收問題。一種解決方案是捕獲並重新拋出所有的異常。但是,這種解決方案比較乏味,因爲需要在兩個地方清除所分配的資源。一個在正常的代碼中;另一個在異常代碼中。

Java有一種更好的解決方案,這就是finally子句。如果使用Java編寫數據庫程序,就需要使用同樣的技術關閉與數據庫的連接。當發生異常時,恰當地關閉所有數據庫的連接是非常重要的。

不管是否有異常被捕獲,finally子句中的代碼都被執行。在下面的示例中,程序將在所有情況下關閉文件:

InputStream in = new FileInputStream(...);
try {
	// 1
	...
	// 2
} catch (IOException e) {
	// 3
	...
	// 4
} finally {
	// 5
	in.close;
}
// 6

在上面這段代碼中,有下列3中情況會執行finally子句:
1) 代碼沒有拋出異常。在這種情況下,程序首先執行try語句塊中的全部代碼,然後執行finally子句中的代碼。隨後,繼續執行try語句塊之後的第一條語句。也就是說,執行標註的1、2、5、6處。

2) 拋出一個在catch子句中捕獲的異常。在上面的示例中就是IOException異常。在這種情況下,程序將執行try語句塊中的所有代碼,直到發生異常爲止。此時,將跳過try語句塊中的剩餘代碼,轉去執行與該異常匹配的catch子句中的代碼,最後執行finally子句中的代碼。
如果catch子句沒有拋出異常,程序將執行try語句塊之後的第一條語句。在這裏,執行標註1、3、4、5、6處的語句。
如果catch子句拋出了一個異常,異常將被拋回這個方法的調用者。在這裏,執行標註1、3、5處的語句。

3) 代碼拋出了一個異常,但這個異常不是由catch子句捕獲的。在這種情況下,程序將執行try語句塊中的所有語句,直到異常被拋出爲止。此時,將跳過try語句塊中的剩餘代碼,然後執行finally子句中的語句,並將異常拋給這個方法的調用者。在這裏,將執行標註1、5處的語句。

try語句可以只有finally子句,而沒有catch子句。如:

InputStream in = ....;
try {
	...
} finally {
	in.close();
}

無論在try語句塊中是否遇到異常,finally子句中的in.close()語句都會被執行。當然,如果真的遇到一個異常,這個異常將會被重新拋出,並且必須由另一個catch子句捕獲。

有時候,finally子句也會帶來麻煩。例如,清理資源的方法也有可能拋出異常。假設希望能夠確保在流處理代碼中遇到異常時將流關閉。

InputStream in = ....;
try {
	...
} finally {
	in.close();
}

現在,假設在try語句塊中的代碼拋出了一些非IOException的異常,這些異常只有這個方法的調用者才能夠給予處理。執行finally語句塊,並調用close方法。而close方法本身也有可能拋出IOException異常。當出現這種情況時,原始的異常將會丟失,轉而拋出close方法 的異常。

這會有問題,因爲第一個異常將很可能更有意思。如果你想做適當的處理,重新拋出原來的異常,代碼會變得極其繁瑣:

InputStream in = ...;
Exception ex = null;
try {
	try {
		...
	} catch (Exception e) {
		ex = e;
		throw e;
	} 
} finally {
	try {
		in.close()
	} catch(Exception e) {
		if (ex == null) throw e;
	}
}

幸運的是,在Java SE 7中提供了關閉資源更方便的方法。

帶資源的try語句

對於以下代碼模式:

open a resource
try {
	...
} finally {
	close the resource
}

假設資源屬於一個實現了AutoCloseable接口的類,Java SE 7爲這種代碼模式提供了一個很有用的快捷方式。AutoCloseable接口有一個方法:

void close() throws Exception

帶資源的try語句(try - with - resources)的最簡形式爲:

try (Resource res = ...) {
	...
}

try塊退出時,會自動的調用res.close()。下面給出一個典型的例子,這裏讀取一個文件中所有單詞:

try (Scanner in = new Scanner(new FileInputStream("/usr/share/dict/words"), "UTF-8")) {
	while (in.hasNext())
		System.out.println(in.next());
}

這個塊正常退出時,或者存在一個異常時,都會調用in.close()方法,就好像使用了finally塊一樣。

還可以指定多個資源。如:

try (Scanner in = new Scanner(new FileInputStream("/usr/share/dict/words"), "UTF-8");
	PrintWriter out = new PrintWriter("out.txt")) {
	while (in.hasNext())
		out.println(in.next().toUpperCase());
}

不論這個塊如何退出,in和out都會關閉。如果你用常規方式手動編程,就需要兩個嵌套的try/finally語句。

在上面的問題中,如果try塊拋出一個異常,而且close方法也拋出一個異常,這就會帶來一個難題。帶資源的try語句可以很好的處理這種情況。原來的異常會重新拋出,而close方法拋出的異常會“被抑制”。這些異常將自動捕獲,並由addSuppressed方法增加到原來的異常。如果對這些異常感興趣,可以調用getSuppressed方法,它會得到從close方法拋出並被抑制的異常列表。

只要需要關閉資源,就要儘可能使用帶資源的try語句。

捐贈

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

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