《Java從小白到大牛》紙質版已經上架了!!!
釋放資源
有時在try-catch語句中會佔用一些非Java資源,如:打開文件、網絡連接、打開數據庫連接和使用數據結果集等,這些資源並非Java資源,不能通過JVM的垃圾收集器回收,需要程序員釋放。爲了確保這些資源能夠被釋放可以使用finally代碼塊或Java 7之後提供自動資源管理(Automatic Resource Management)技術。
finally代碼塊 {#finally}
try-catch語句後面還可以跟有一個finally代碼塊,try-catch-finally語句語法如下:
try{
//可能會生成異常語句
} catch(Throwable e1){
//處理異常e1
} catch(Throwable e2){
//處理異常e1
} catch(Throwable eN){
//處理異常eN
} finally{
//釋放資源
}
無論try正常結束還是catch異常結束都會執行finally代碼塊,如同14-2所示。
使用finally代碼塊示例代碼如下:
//HelloWorld.java文件
package com.a51work6;
… …
public class HelloWorld {
public static void main(String[] args) {
Date date = readDate();
System.out.println("讀取的日期 = " + date);
}
public static Date readDate() {
FileInputStream readfile = null;
InputStreamReader ir = null;
BufferedReader in = null;
try {
readfile = new FileInputStream("readme.txt");
ir = new InputStreamReader(readfile);
in = new BufferedReader(ir);
// 讀取文件中的一行數據
String str = in.readLine();
if (str == null) {
return null;
}
DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
Date date = df.parse(str);
return date;
} catch (FileNotFoundException e) {
System.out.println("處理FileNotFoundException...");
e.printStackTrace();
} catch (IOException e) {
System.out.println("處理IOException...");
e.printStackTrace();
} catch (ParseException e) {
System.out.println("處理ParseException...");
e.printStackTrace();
} finally { ①
try {
if (readfile != null) {
readfile.close(); ②
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (ir != null) {
ir.close(); ③
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (in != null) {
in.close(); ④
}
} catch (IOException e) {
e.printStackTrace();
}
} ⑤
return null;
}
}
上述代碼第①行~第⑤行是finally語句,在這裏通過關閉流釋放資源,FileInputStream、InputStreamReader和BufferedReader是三個輸入流,它們都需要關閉,見代碼第②行~第④行通過流的close()關閉流,但是流的close()方法還有可以能發生IOException異常,所以這裏又針對每一個close()語句還需要進行捕獲處理。
注意 爲了代碼簡潔等目的,可能有的人會將finally代碼中的多個嵌套的try-catch語句合併,例如將上述代碼改成如下形式,將三個有可以發生異常的close()方法放到一個try-catch。讀者自己考慮一下這處理是否穩妥呢?每一個close()方法對應關閉一個資源,如果第一個close()方法關閉時發生了異常,那麼後面的兩個也不會關閉,因此如下的程序代碼是有缺陷的。
try {
... ...
} catch (FileNotFoundException e) {
... ...
} catch (IOException e) {
... ...
} catch (ParseException e) {
... ...
} finally {
try {
if (readfile != null) {
readfile.close();
}
if (ir != null) {
ir.close();
}
if (in != null) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
### 自動資源管理 {#-0}
14.4.1節使用finally代碼塊釋放資源會導致程序代碼大量增加,一個finally代碼塊往往比正常執行的程序還要多。在Java 7之後提供自動資源管理(Automatic Resource Management)技術,可以替代finally代碼塊,優化代碼結構,提高程序可讀性。
自動資源管理是在try語句上的擴展,語法如下:
```java
try (聲明或初始化資源語句) {
//可能會生成異常語句
} catch(Throwable e1){
//處理異常e1
} catch(Throwable e2){
//處理異常e1
} catch(Throwable eN){
//處理異常eN
}
在try語句後面添加一對小括號“()”,其中是聲明或初始化資源語句,可以有多條語句語句之間用分號“;”分隔。
示例代碼如下:
//HelloWorld.java文件
package com.a51work6;
… …
public class HelloWorld {
public static void main(String[] args) {
Date date = readDate();
System.out.println("讀取的日期 = " + date);
}
public static Date readDate() {
// 自動資源管理
try (FileInputStream readfile = new FileInputStream("readme.txt"); ①
InputStreamReader ir = new InputStreamReader(readfile); ②
BufferedReader in = new BufferedReader(ir)) { ③
// 讀取文件中的一行數據
String str = in.readLine();
if (str == null) {
return null;
}
DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
Date date = df.parse(str);
return date;
} catch (FileNotFoundException e) {
System.out.println("處理FileNotFoundException...");
e.printStackTrace();
} catch (IOException e) {
System.out.println("處理IOException...");
e.printStackTrace();
} catch (ParseException e) {
System.out.println("處理ParseException...");
e.printStackTrace();
}
return null;
}
}
上述代碼第①行~第③行是聲明或初始化三個輸入流,三條語句放到在try語句後面小括號中,語句之間用分號“;”分隔,這就是自動資源管理技術了,採用了自動資源管理後不再需要finally代碼塊,不需要自己close這些資源,釋放過程交給了JVM。
注意 所有可以自動管理的資源需要實現AutoCloseable接口,上述代碼中三個輸入流FileInputStream、InputStreamReader和BufferedReader從Java 7之後實現AutoCloseable接口,具體哪些資源實現AutoCloseable接口需要查詢API文檔。
throws與聲明方法拋出異常 {#throws}
在一個方法中如果能夠處理異常,則需要捕獲並處理。但是本方法沒有能力處理該異常,捕獲它沒有任何意義,則需要在方法後面聲明拋出該異常,通知上層調用者該方法有可以發生異常。
方法後面聲明拋出使用throws關鍵字,回顧一下10.3.3節成員方法語法格式如下:
class className {
[public | protected | private ] [static] [final | abstract] [native] [synchronized]
type methodName([paramList]) [throws exceptionList] {
//方法體
}
}
其中參數列表之後的[throws exceptionList]語句是聲明拋出異常。方法中可能拋出的異常(除了Error和RuntimeException及其子類外)都必須通過throws語句列出,多個異常之間採用逗號(,)分隔。
注意 如果聲明拋出的多個異常類之間有父子關係,可以只聲明拋出父類。但如果沒有父子關係情況下,最好明確聲明拋出每一個異常,因爲上層調用者會根據這些異常信息進行相應的處理。假如一個方法中有可能拋出IOException和ParseException兩個異常,那麼聲明拋出IOException和ParseException呢?還是隻聲明拋出Exception呢?因爲Exception是IOException和ParseException的父類,只聲明拋出Exception從語法是允許的,但是聲明拋出IOException和ParseException更好一些。
如果將14.3節示例進行修改,在readDate()方法後聲明拋出異常,代碼如下:
//HelloWorld.java文件
package com.a51work6;
… …
public class HelloWorld {
public static void main(String[] args) { ①
try {
Date date = readDate(); ②
System.out.println("讀取的日期 = " + date);
} catch (IOException e) { ③
System.out.println("處理IOException...");
e.printStackTrace();
} catch (ParseException e) { ④
System.out.println("處理ParseException...");
e.printStackTrace();
}
}
public static Date readDate() throws IOException, ParseException { ⑤
// 自動資源管理
FileInputStream readfile = new FileInputStream("readme.txt"); ⑥
InputStreamReader ir = new InputStreamReader(readfile);
BufferedReader in = new BufferedReader(ir);
// 讀取文件中的一行數據
String str = in.readLine(); ⑦
if (str == null) {
return null;
}
DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
Date date = df.parse(str); ⑧
return date;
}
}
由於readDate()方法中代碼第⑥、⑦、⑧行都有可能引發異常。在readDate()方法內又沒有捕獲處理,所有需要在代碼第⑤行方法後聲明拋出異常,事實上有三個異常FileNotFoundException、IOException和ParseException,由於FileNotFoundException屬於IOException異常,所以只聲明IOException和ParseException就可以了。
一旦readDate()方法聲明拋出了異常,那麼它的調用者main()方法,也會面臨同樣的問題:要麼捕獲自己處理,要麼拋出給上層調用者。如果一旦發生異常main()方法也選擇拋出那麼程序運行就會終止。本例中main()方法是捕獲異常進行處理,捕獲異常過程前面已經介紹過了,這裏不再贅述。
自定義異常類
有些公司爲了提高代碼的可重用性,自己開發了一些Java類庫或框架,其中少不了自己編寫了一些異常類。實現自定義異常類需要繼承Exception類或其子類,如果自定義運行時異常類需繼承RuntimeException類或其子類。
實現自定義異常類示例代碼如下:
package com.a51work6;
public class MyException extends Exception { ①
public MyException() { ②
}
public MyException(String message) { ③
super(message);
}
}
上述代碼實現了自定義異常,自定義異常類一般需要提供兩個構造方法,一個是代碼第②行的無參數的默認構造方法,異常描述信息是空的;另一個是代碼第③行的字符串參數的構造方法,message是異常描述信息,getMessage()方法可以獲得這些信息。
自定義異常就這樣簡單,主要是提供兩個構造方法就可以了,
throw與顯式拋出異常 {#throw}
Java異常相關的關鍵字中有兩個非常相似,它們是throws和throw,其中throws關鍵字前面14.5節已經介紹了,throws用於方法後聲明拋出異常,而throw關鍵字用來人工引發異常。本節之前讀者接觸到的異常都是由於系統生成的,當異常發生時,系統一個異常對象,並將其拋出。但也可以通過throw語句顯式拋出異常,語法格式如下:
throw Throwable或其子類的實例
所有Throwable或其子類的實例都可以通過throw語句拋出。
顯式拋出異常目的有很多,例如不想某些異常傳給上層調用者,可以捕獲之後重新顯式拋出另外一種異常給調用者。
修改14.4節示例代碼如下:
//HelloWorld.java文件
package com.a51work6;
… …
public class HelloWorld {
public static void main(String[] args) {
try {
Date date = readDate();
System.out.println("讀取的日期 = " + date);
} catch (MyException e) {
System.out.println("處理MyException...");
e.printStackTrace();
}
}
public static Date readDate() throws MyException {
// 自動資源管理
try (FileInputStream readfile = new FileInputStream("readme.txt");
InputStreamReader ir = new InputStreamReader(readfile);
BufferedReader in = new BufferedReader(ir)) {
// 讀取文件中的一行數據
String str = in.readLine();
if (str == null) {
return null;
}
DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
Date date = df.parse(str);
return date;
} catch (FileNotFoundException e) { ①
throw new MyException(e.getMessage()); ②
} catch (IOException e) { ③
throw new MyException(e.getMessage()); ④
} catch (ParseException e) {
System.out.println("處理ParseException...");
e.printStackTrace();
}
return null;
}
}
如果軟件設計者不希望readDate()方法中捕獲的FileNotFoundException和IOException異常出現在main()方法(上層調用者)中,那麼可以在捕獲到FileNotFoundException和IOException異常時,通過throw語句顯式拋出一個異常,見代碼第②行和第④行throw new MyException(e.getMessage())語句,MyException是自定義的異常。
注意 throw顯式拋出的異常與系統生成並拋出的異常,在處理方式上沒有區別,就是兩種方法:要麼捕獲自己處理,要麼拋出給上層調用者。在本例中是聲明拋出,所以在readDate()方法後面要聲明拋出MyException異常。
本章小結
本章介紹了Java異常處理機制,其中包括Java異常類繼承層次、捕獲異常、釋放資源、throws、throw和自定義異常類。讀者需要重點掌握捕獲異常處理,熟悉throws和throw的區分和用法。
配套視頻
http://edu.51cto.com/topic/1246.html
配套源代碼
http://www.zhijieketang.com/group/5