《Java從小白到大牛》之第14章 異常處理(下)

《Java從小白到大牛》之第14章 異常處理(下)

《Java從小白到大牛》

釋放資源

有時在try-catch語句中會佔用一些非Java資源,如:開啟檔案、網路連線、開啟資料庫連線和使用資料結果集等,這些資源並非Java資源,不能透過JVM的垃圾收集器回收,需要程式設計師釋放。為了確保這些資源能夠被釋放可以使用finally程式碼塊或Java 7之後提供自動資源管理(Automatic Resource Management)技術。

finally程式碼塊

try-catch語句後面還可以跟有一個finally程式碼塊,try-catch-finally語句語法如下:

try{

//可能會生成異常語句

} catch(Throwable e1){

//處理異常e1

} catch(Throwable e2){

//處理異常e12

} catch(Throwable eN){

//處理異常eN

} finally{

//釋放資源

}

無論try正常結束還是catch異常結束都會執行finally程式碼塊,如圖同14-2所示。

《Java從小白到大牛》之第14章 異常處理(下)

圖14-2finally程式碼塊流程

使用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();

}

}

自動資源管理

14。4。1節使用finally程式碼塊釋放資源會導致程式程式碼大量增加,一個finally程式碼塊往往比正常執行的程式還要多。在Java 7之後提供自動資源管理(Automatic Resource Management)技術,可以替代finally程式碼塊,最佳化程式碼結構,提高程式可讀性。

自動資源管理是在try語句上的擴充套件,語法如下:

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關鍵字,回顧一下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與顯式丟擲異常

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的區分和用法。