10.3.3 捕獲異常及異常處理
在整個異常處理機制中,異常在系統中進行傳遞,傳遞到程序員認為合適的位置,就捕獲到該異常,然後進行邏輯處理,使得項目不會因為出現異常而崩潰。
為了捕獲異常並對異常進行處理,使用的捕獲異常以及處理的語法格式為:
try{
//邏輯代碼
}catch(異常類名 參數名){
//處理代碼
}
在該語法中,將正常的程序邏輯代碼書寫在try語句塊內部進行執行,這些代碼為可能拋出異常的代碼,而catch語句中書寫對應的異常類的類名,在catch語句塊內部書寫出現該類型的異常時的處理代碼。
程序執行到try-catch語句時,如果沒有發生異常,則完整執行try語句塊內部的所有代碼,而catch語句塊內部的代碼不會執行,如果在執行時發生異常,則從發生異常的代碼開始,後續的try語句塊代碼不會執行,而跳轉到該類型的異常對應的catch語句塊中。
示例代碼如下:
String s = "123";
try{
int n = Integer.parseInt(s);
System.out.println(n);
}catch(NumberFormatException e){
System.out.println("該字符串無法轉換!");
}
在該示例代碼中,Integer類的parseInt方法可能會拋出NumberFormatException,因為parseInt方法的聲明如下:
public static int parseInt(String s) throws NumberFormatException
這裡字符串s轉換為int沒有發生異常,則程序執行完try語句塊內部的代碼,程序的運行結果為:
123
如果將字符串對象s的值修改為”abc”,則運行上面的代碼,則parseInt方法執行時將拋出NumberFormatException,則調用parseInt方法語句後續的try語句塊中的代碼不會執行,程序的執行流程跳轉到捕獲NumberFormatException異常的catch語句塊內部,然後執行該catch語句塊內部的代碼,則程序的執行結果是:
該字符串無法轉換!
這就是最基本的捕獲異常和異常處理的代碼結構。使用try語句捕獲程序執行時拋出的異常,使用catch語句塊匹配拋出的異常類型,在catch語句塊內部書寫異常處理的代碼。
在實際程序中,也可以根據異常類型的不同進行不同的處理,這樣就需要多個catch語句塊,其結構如下:
try{
//邏輯代碼
} catch(異常類名1 參數名1){
//處理代碼1
} catch(異常類名2 參數名2){
//處理代碼2
}
……
}catch(異常類名n 參數名n){
//處理代碼n
}
例如:
String s = "123";
try{
int n = Integer.parseInt(s);
System.out.println(n);
char c = s.charAt(4);
System.out.println(c);
}catch(NumberFormatException e){
System.out.println("該字符串無法轉換!");
}catch(StringIndexOutOfBoundsException e){
System.out.println("字符串索引值越界");
}
在執行時,按照catch語句塊書寫的順序從上向下進行匹配,直到匹配到合適的異常就結束try-catch語句塊的執行。
在實際執行時,就可以根據捕獲的異常類型不同,書寫不同的異常處理的代碼了。使用該語法時需要注意,如果這些異常類直接存在繼承關系,則子類應該書寫在上面,父類應該書寫在下面,否則將出現語法錯誤。例如:
String s = "123";
try{
int n = Integer.parseInt(s);
System.out.println(n);
char c = s.charAt(4);
System.out.println(c);
}catch(Exception e){
}catch(NumberFormatException e){ //語法錯誤,異常已經被處理
System.out.println("該字符串無法轉換!");
}catch(StringIndexOutOfBoundsException e){ //語法錯誤,異常已經被處理
System.out.println("字符串索引值越界");
}
這裡Exception類是所有異常類的父類,在匹配時可以匹配到所有的異常,所有後續的兩個異常處理的代碼根本不會得到執行,所以將出現語法錯誤。正確的代碼應該為:
String s = "123";
try{
int n = Integer.parseInt(s);
System.out.println(n);
char c = s.charAt(4);
System.out.println(c);
}catch(NumberFormatException e){
System.out.println("該字符串無法轉換!");
}catch(StringIndexOutOfBoundsException e){
System.out.println("字符串索引值越界");
}catch(Exception e){ }
如果在程序執行時,所有的異常處理的代碼都是一樣的,則可以使用Exception代表所有的異常,而不需要一一進行區分,示例代碼如下:
String s = "123";
try{
int n = Integer.parseInt(s);
System.out.println(n);
char c = s.charAt(4);
System.out.println(c);
}catch(Exception e){
//統一的處理代碼
}
在實際使用時,由於try-catch的執行流程,使得無論是try語句塊還是catch語句塊都不一定會被完全執行,而有些處理代碼則必須得到執行,例如文件的關閉和網絡連接的關閉等,這樣如何在try語句塊和catch語句塊中都書寫則顯得重復,而且容易出現問題,這樣在異常處理的語法中專門設計了finally語句塊來進行代碼的書寫。語法保證finally語句塊內部的代碼肯定獲得執行,即使在try或catch語句塊中包含return語句也會獲得執行,語法格式如下:
finally{
//清理代碼
}
該語法可以和上面的兩種try-catch語句塊匹配,也可以和try語句匹配使用,和try語句塊匹配的語法格式如下:
try{
//邏輯代碼
}finally{
//清理代碼
}
這樣在執行時,無論try語句塊中的語句是否發生異常,finally語句塊內部的代碼都會獲得執行。
而只書寫finally而不書寫catch則會導致異常的丟失,所以最常用的異常處理的語法格式還是如下語法:
try{
//邏輯代碼
}catch(異常類名 參數){
//異常處理代碼
}finally{
//清理代碼
}
這樣就是整個異常處理部分的邏輯代碼、異常處理代碼和清理的代碼成為一個整體,使得程序代碼更加顯得緊湊,便於代碼的閱讀和使用。
最後,介紹一下使用異常處理語法時需要注意的問題:
1、書寫在try語句塊內部的代碼執行效率比較低。所以在書寫代碼時,只把可能出現異常的代碼書寫在try語句塊內部。
2、如果邏輯代碼中拋出的異常屬於RuntimeException的子類,則不強制書寫在try語句塊的內部,如果拋出的異常屬於非RuntimeException的子類,則必須進行處理,其中書寫在try語句塊是一種常見的處理方式。
3、catch語句塊中只能捕獲try語句塊中可能拋出的異常,否則將出現語法錯誤。
10.3.4 聲明自定義異常類
如果JDK API中提供的已有的異常類無法滿足實際的使用需要,則可以根據需要聲明自定義的異常類來代表項目中指定類型的異常。
異常類在語法上要求必須直接或間接繼承Exception,可以根據需要選擇繼承Exception或RuntimeException類,這樣也設定了自定義異常類的類型。如果直接繼承Exception,則屬於必須被處理的異常,如果繼承RuntimeException,則不強制必須被處理。當然,可以根據需要繼承其它Exception的子類。
在編碼規范上,一般將異常類的類名命名為XXXException,其中XXX用來代表該異常的作用。
示例代碼如下:
/**
* 自定義異常類
*/
public class MyException extends RuntimeException {}
自定義異常類的用途,則完全由程序員進行規定,可以在出現該異常類型的條件時拋出該異常,則就可以代表該類型的異常了。
在實際的項目中,有些時候還需要設計專門的異常類體系,來代表各種項目中需要代表的異常情況。
10.4 異常處理方式
前面介紹了異常處理機制的相關語法,但是當出現異常時,如何進行處理是語法無法解決的問題,下面就介紹一下異常處理的方式。
異常處理,顧名思義就是將出現的異常處理掉,但是根據異常出現的位置以及異常的類型不同,會出現很多的方式,依次介紹如下:
1、不處理
該處理方式就是只捕獲異常不進行處理。不推薦使用該方式。
例如:
String s = “abc”;
try{
int n = Integer.parseInt(s);
}catch(Exception e){
}
對於這樣的處理,該異常被忽略掉了,有可能會影響後續邏輯的執行執行。該種處理方式一般被初學者使用的比較多。
2、直接處理掉
如果具備處理異常的條件,則可以根據不同的異常情況將該異常處理掉,例如給出用戶錯誤原因的提示等或根據邏輯上的需要修正對應的數值。
例如:
/**
* 異常處理示例
* 該程序的功能是將用戶輸出的命令行參數轉換為數字並輸出
*/
public class ExceptionHandle1 {
public static void main(String[] args) {
int n = 0;
try{
//將輸入的第一個命令行參數轉換為數字
n = Integer.parseInt(args[0]);
//輸出轉換後的結果
System.out.println(n);
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("請輸入命令行參數!");
}catch(NumberFormatException e){
System.out.println("命令行參數不是數字字符串!");
}
}
}
在執行該代碼時,如果發生數組下標越界異常,則代表用戶未輸入命令行參數,則提示用戶輸入命令行參數,如果發生數字格式化異常,則代表用戶輸入的第一個命令行參數不是數字字符串,則給出用戶對應的提示信息。
該示例中就是根據異常出現的原因提示用戶進行正確的操作,這是一種常見的異常處理的方式。
3、重新拋出
在實際的項目,有些時候也需要將某個方法內部出現的所有異常都轉換成項目中自定義的某個異常類,然後再重新拋出。
例如:
try{
//邏輯代碼
}catch(Exception e){
throw new MyException();
}
這樣轉換以後,比較容易發現異常出現的位置,也方便項目中邏輯上的處理。
4、在方法中聲明
如果當前方法或構造方法中,或在當前方法或構造方法中調用的其它方法,會拋出某個異常,而在當前方法或構造方法中沒有足夠的信息對該異常進行處理,則可以將該異常進行傳遞,將該異常傳遞給調用當前方法的位置在進行處理。這就是異常在系統中傳遞的方式。
例如:
/**
* 異常處理示例2
* 演示在方法中重新聲明異常
*/
public class ExceptionHandle2 {
public void test(String s) throws NumberFormatException{
int n = Integer.parseInt(s);
System.out.println(n);
}
}
在test方法中,由於parseInt方法會拋出NumberFormatException異常,而該方法內部無法對此進行處理,所以則在test方法內部聲明把NumberFormatException異常拋出,這樣該異常就由調用test的方法再進行處理。這樣異常就可以在方法之間進行傳遞了。
這裡列舉了異常處理中常見的一些處理方式,當然也可以根據需要,進行其它的處理。
10.5 總結
異常處理是Java語言中增強程序健壯性的一種機制,Java語法為異常處理提供了一系列的語法格式,在實際的項目中,需要根據具體的情況對異常給出對應的處理,使得異常能夠被正確的處理掉,從而保證項目的正常執行。