Java異常處理
異常是運行時在代碼序列中引起的非正常狀態。在不支持異常處理的計算機語言中,必須手動檢查和處理錯誤,Java語言則採用面向對象的方式管理運行時錯誤。
一、概念
Java異常是用來描述在一段代碼中發生的異常情況的對象。當出現引起異常的情況時,就會創建用來表示異常的對象,並在引起異常的方法中拋出異常對象。方法可以選擇自己處理異常或者傳遞異常交由其他方法來處理。
Java異常處理通過五個關鍵字進行管理:try、catch、throw、throws、finally。
在try代碼快中封裝可能發生異常的程序語句,對這些語句進行監視。如果在try代碼塊中發生異常,就會將異常拋出。代碼使用catch來捕獲異常,並可以定義一些方法來處理異常。系統生成的異常由Java運行時系統自動拋出。爲了手動拋出異常,需要使用throw關鍵字。從方法中拋出的任何異常都必須通過一條throws子句進行指定。在try代碼塊結束之後必須執行的所有代碼則需要放在finally代碼塊中。
二、異常類型
所有異常類型都是內置類Throwable的子類,Throwable的兩個子類將異常分爲兩個不同的分支。一個分支是Exception類,這個類既可以用於用戶程序當前捕獲的異常情況,也可以用於創建自定義異常類型的子類。另一個分支是Error類,該類定義了在常規環境下不希望由程序捕獲的異常。Error類型的異常由Java運行時系統使用,以指示運行時系統本身發生了某些錯誤。
三、未捕獲的異常
沒有被程序捕獲的所有異常,最終都會交由Java運行時系統提供的默認處理程序捕獲。默認處理程序會顯示一個描述異常的字符串,輸出異常發生的堆棧蹤跡並終止程序。
例如,運行如下代碼:
public class Demo {
public static void main(String[] args) {
int a=2/0;
}
}
生成的異常信息:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Demo.main(Demo.java:5)
拋出的異常的類型是Exception的子類ArithmeticException,即算術異常,更具體地描述了發生的錯誤類型。Java提供了一些能與可能產生的各種運行時錯誤相匹配的內置異常類型。
四、使用try和catch
進行主動的異常處理有兩個優點:第一,語序允許修復錯誤;第二,阻止程序自動終止
爲了主動處理運行時錯誤,可以在try代碼塊中封裝希望監視的代碼,之後通過catch子句指定希望捕獲的異常類型。
例如,參照如下代碼:
public static void main(String[] args) {
try{
int a=10/0;
System.out.println("輸出語句1");
}catch(ArithmeticException e){
System.out.println("輸出語句2");
}
System.out.println("輸出語句3");
}
輸出結果是:
輸出語句2
輸出語句3
代碼在進行除零操作時發生了異常,此時“輸出語句1”將無法得到被調用的機會。一旦拋出異常,程序控制就會從try代碼塊中轉移出來,進入catch代碼塊中。執行了catch語句後,就會繼續運行整個try/catch代碼塊的下一行。
五、多條catch子句
在有些情況下,一個代碼塊可能會引發多個異常。對於這種情況,需要指定兩條或多條catch子句,用於捕獲不同類型的異常。當拋出異常時,按順序檢查每條catch語句,執行類型和異常相匹配的第一條catch子句,忽略其他catch子句,並繼續執行try/catch代碼塊後面的代碼。
需要注意的是,異常子類必須位於異常超類之前,因爲使用了某個超類的catch語句會捕獲這個超類及其所有子類的異常。因此,如果子類位於超類之後的話,永遠也不會到達子類。不可到達的代碼會被編譯器提示錯誤。
參照如下代碼,通過Math的靜態方法random來隨機產生0和1兩個隨機數,生成的不同數值就會引發不同的異常,分別由不同的catch子句進行處理。
public static void main(String[] args) {
int random=(int) Math.round(Math.random());
try{
int a=10/random;
int[] array={10};
array[random]=1;
}catch (ArithmeticException e) {
System.out.println("ArithmeticException");
}catch (ArrayIndexOutOfBoundsException e) {
System.out.println("ArrayIndexOutOfBoundsException");
}
System.out.println("代碼塊結束");
}
此外,也可以通過多重捕獲的方式來使用相同的catch子句捕獲兩個或更多個異常。在catch子句中使用或運算符(|)分隔每個異常,每個多重捕獲參數都被隱式地聲明爲final類型。
public static void main(String[] args) {
int random=(int) Math.round(Math.random());
try{
int a=10/random;
int[] array={10};
array[random]=1;
}catch(ArithmeticException | ArrayIndexOutOfBoundsException e){
System.out.println("兩個異常之一");
}
}
六、throw
在之前的例子中,捕獲的異常都是由Java運行時系統拋出的異常,可以通過throw語句顯式地拋出異常。
throw的一般形式如下所示:
throw ThrowableInstance
ThrowableInstance必須是Throwable或其子類類型的對象。throw語句之後的執行流程會立即停止,所有的後續語句都不會執行,然後按順序依次檢查所有的catch語句,檢查是否和異常類型相匹配。如果沒有找到匹配的catch語句,那麼默認的異常處理程序會終止程序並輸出堆棧蹤跡。
例如,運行以下代碼,將得到輸出結果:“NullPointerException”。
public class Demo {
public static void demo(){
throw new NullPointerException("NullPointer");
}
public static void main(String[] args) {
try{
demo();
}catch (NullPointerException e) {
System.out.println("NullPointerException");
}
}
}
但如果catch子句的異常與throw拋出的異常類型不匹配時,異常將由Java默認的異常處理程序來處理。
例如,運行以下代碼:
public class Demo {
public static void demo(){
throw new NullPointerException("NullPointer");
}
public static void main(String[] args) {
try{
demo();
}catch (ArrayIndexOutOfBoundsException e) {
System.out.println("ArrayIndexOutOfBoundsException");
}
}
}
運行結果是:
Exception in thread "main" java.lang.NullPointerException: NullPointer
at Demo.demo(Demo.java:4)
at Demo.main(Demo.java:9)
七、throws
如果方法可能引發自身不進行處理的異常,就必須通過throws關鍵字來向方法的調用者指明要拋出的異常類型。
方法可能拋出的所有異常都必須在throws子句中進行聲明,否則將產生編譯時錯誤。
例如,參照以下代碼,該程序試圖拋出無法匹配的異常,因爲程序沒有指定throws子句來聲明這一事實,所以程序無法編譯。
public class Demo {
public static void demo(){
throw new IllegalAccessException("IllegalAccess");
}
public static void main(String[] args) {
demo();
}
}
爲了使代碼能夠運行,需要將之進行如下修改:
public class Demo {
public static void demo() throws IllegalAccessException{
throw new IllegalAccessException("IllegalAccess");
}
public static void main(String[] args) {
try {
demo();
} catch (IllegalAccessException e) {
System.out.println(e.getMessage());
}
}
}
八、finally
當拋出異常後,方法的執行流程將不會按照原先的順序進行,這對於某些方法來說是個問題。例如,如果在方法中打開了一個文件,我們並不希望因爲拋出了異常導致關閉文件的代碼被跳過而不執行。finally關鍵字就是來解決這種情況的。
使用finally可以創建一個代碼塊,該代碼塊會在執行try/catch代碼塊之後執行,且在執行try/catch代碼塊後面的代碼之前執行。不管是否有異常拋出,都將執行finally代碼塊。
只要方法從try/catch代碼塊內部返回到調用者,不管是通過未捕獲的異常還是使用顯式的返回語句,都會在方法返回之前執行finally子句。
例如,如下的輸出結果將是:2
public class Demo {
public static int demo(){
try{
int a=10/0;
System.out.println(a);
}catch(Exception e){
return 1;
}finally {
return 2;
}
}
public static void main(String[] args) {
System.out.println(demo());
}
}
九、鏈式異常
鏈式異常用於爲異常關聯另一個異常,第二個異常用於描述當前異常產生的原因。例如,某個方法從文件讀取數值來作爲除法運算的除數,由於發生了I/O錯誤導致獲取到的數值是0,從而導致了ArithmeticException異常。
如果想要讓代碼調用者知道背後的原因是I/O錯誤,使用鏈式異常就可以來處理這種情況以及其他存在多層異常的情況
爲了使用鏈式異常,Throwable有兩個構造函數和兩個方法用於處理這種情況。
兩個構造函數:
Throwable(Throwable cause)
Throwable(String message, Throwable cause)
cause即是用於指定引發當前異常的背後原因,message則可以用於同時指定異常描述。
兩個方法:
Throwable getCause()
Throwable initCause(Throwable cause)
getCause() 方法用來返回引發當前異常的異常,如果不存在背後異常則返回null。
initCause(Throwable cause) 方法將cause和明面上的異常關聯到一起,並返回對異常的引用。因此可以在創建異常之後將異常和背後異常關聯到一起。但是,背後異常只能設置一次,即initCause(Throwable cause) 方法只能調用一次。此外,如果通過構造函數設置了背後異常,也不能再使用該方法來設置背後異常了。
例如,參照如下代碼:
public class Demo {
public static void demo(){
NullPointerException nullPointerException=new NullPointerException("nullPointer");
nullPointerException.initCause(new ArithmeticException("Arithmetic"));
throw nullPointerException;
}
public static void main(String[] args) {
try {
demo();
} catch (Exception e) {
System.out.println(e.getMessage());
System.out.println(e.getCause().getMessage());
}
}
}
運行結果是:
nullPointer
Arithmetic
鏈式異常可以包含所需要的任意深度,但是,過長的異常鏈可能是一種不良的設計。