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