異常概述
異常也稱為例外,是在程序運行過程中發生的並且會打斷程序正常執行的事件,比如算術異常、空指針異常、文件找不到異常。所以在程序設計時,必須考慮到可能發生的異常事件,並做出相應的處理。這樣才能保證程序可以正常運行。
Java的異常處理機制也秉承着面向對象的基本思想,在Java中,所有的異常都是以類的形式存在,除了內置的異常類之外,Java也可以自定義異常類。此外,Java的異常處理機制也允許自定義拋出異常。
為什么要使用異常
在沒有異常處理的語言中,就必須使用if或switch等語句,配合所想得到的錯誤狀況來捕捉程序里所有可能發生的錯誤。但為了捕捉這些錯誤,編寫出來的代碼經常有很多的if語句,有時候這樣也未必能捕捉到所有的錯誤,而且這樣做勢必導致程序運行效率的降低。Java的異常處理機制恰好改進了這一點,它易於使用並且可自行定義異常類,但是不得不說,異常也是一個較為耗費性能的操作,因此我們需要合理地利用Java的異常處理機制,以增進程序的穩定性和效率。
異常的繼承架構
異常可分為兩大類:java.lang.Exception與java.lang.Error,這兩個類均繼承自java.lang.Throwable,上圖為Throwable類的繼承關系圖。
習慣上將Error與Exception統稱為異常類,但這兩者本質上還是有不同的:
1、Error專門用來處理嚴重影響程序運行的錯誤,可是通常程序設計者不會涉及程序代碼去捕捉這種錯誤,其原因在於即使捕捉到它,也無法給予適當的處理,比如Java虛擬機出錯就屬於一種Error
2、Exception包含了一般性的異常,這些異常通常在捕捉到之后便可以做妥善的處理,以確保程序繼續運行
從繼承架構圖中可以看出,Exception擴展出數個子類,其中IOException、RuntimeException是較常用的兩種。RuntimeException即使不編寫異常處理的程序代碼,依然可以編譯成功,而這種異常必須是在程序運行時才有可能發生,比如數組索引值超出了范圍。與RuntimeException不同的是,IOException一定要編寫異常處理的程序代碼才行,它通常用來處理輸入/輸出相關的操作,如對文件的訪問、網絡的連接等。
當異常發生時,發生異常的語句代碼會拋出一個異常類的實例化對象,之后此對象與catch語句中的類的類型進行匹配,然后在相應的catch中進行處理。
try、catch、finally、throw、throws
當異常發生時,通常可以用兩種方法來處理:
1、交由Java默認的異常處理機制做處理,但這種處理方式,Java通常只能輸出異常信息,接着便終止程序的運行,比如:
1 public static void main(String[] args) throws InterruptedException 2 { 3 operateNull(null); 4 System.out.println("After operateNull"); 5 } 6 7 public static char operateNull(String str) 8 { 9 return str.charAt(0); 10 }
返回結果為:
Exception in thread "main" java.lang.NullPointerException at com.xrq.test33.TestMain.operateNull(TestMain.java:13) at com.xrq.test33.TestMain.main(TestMain.java:7)
和"接着便終止程序的運行"一致,因為第4行的語句並沒有打印。
2、自行編寫try-catch-finally塊來捕捉異常,這種寫法的最大好處是,可以靈活操控程序的流程且可做出最適當的處理,比如:
1 public static void main(String[] args) throws InterruptedException 2 { 3 try 4 { 5 Thread.sleep(1000); 6 operateNull(null); 7 } 8 catch (NullPointerException e) 9 { 10 System.out.println("NullPointerException's catch block!"); 11 throw e; 12 } 13 finally 14 { 15 System.out.println("NullPointerException's finally block!"); 16 } 17 } 18 19 public static char operateNull(String str) 20 { 21 return str.charAt(0); 22 }
返回結果為:
1 NullPointerException's catch block! 2 NullPointerException's finally block! 3 Exception in thread "main" java.lang.NullPointerException 4 at com.xrq.test33.TestMain.operateNull(TestMain.java:25) 5 at com.xrq.test33.TestMain.main(TestMain.java:10)
關於這段代碼解釋每個關鍵字:
(1)try表示要檢查的程序語句
(2)catch表示異常發生時的處理語句,可省略
(3)finally表示無論catch內部寫了什么,即使是return,也都會運行到的語句,可省略。finally常用於對某段待檢查的代碼做掃尾工作,比如ReentrantLock的unlock()、IO的close()
(4)throw表示拋出一個類的實例,注意實例二字,實例意味着throw出去的是一個實例化的異常對象。所以代碼的11行也可以寫為"throw new NullPointerException()",至於為什么不這么寫,因為寫"throw new NullPointerException()"並沒有返回結果中第4行、第5行中的異常堆棧。注意,throw關鍵字可以寫在任何地方,並不強制必須寫在catch塊中,運行到throw所在的行,打印異常並立即退出當前方法
(5)throws用於方法聲明,表示如果方法內的程序代碼可能會發生異常,且方法內又沒有使用代碼的代碼塊來捕捉這些異常時,則必須在聲明方法時一並指明所有可能發生的異常,以便讓調用此方法的程序得以做好准備來捕捉異常。比如"方法名稱(參數...) throws 異常類1, 異常類2, 異常類3...",也可以不這么麻煩,Exception是所有異常的父類,因此也可以直接"方法名稱(參數...) throws Exception"
異常場景匯總
異常細節諸多,之前我也一直有搞不清楚的地方,因此這里對多種場景做一個總結、匯總:
1、接口方法可以throws異常,但必須throws一個具體的異常,不能直接throws出去Exception
public interface InterfaceException { void ExceptionMethod() throws NullPointerException; }
2、接口方法throws異常,其實現類實現該方法的時候不強制必須拋出該異常,也可以任意拋出異常
public class InterfaceExceptionImpl implements InterfaceException { public void ExceptionMethod() { } }
public class InterfaceExceptionImpl implements InterfaceException { public void ExceptionMethod() throws ArrayIndexOutOfBoundsException { } }
3、catch塊內如果捕獲到了異常並且throw出去了e,那么方法之后的代碼都不會再運行了;catch塊內如果捕獲到了異常,但是沒有throw出去e,也沒有任何導致程序終止的語句,那么try...catch...finally之后的語句仍然可以繼續運行
4、捕獲異常不可以先catch (Exception e){...}再catch (NullPointerException e)
原因是"Unreachable catch block for NullPointerException. It is already handled by the catch block for Exception",即Java檢測到NullPointer這個異常是不可達的
5、方法A聲明throws異常,則調用方法A的代碼必須try...catch...該異常
6、方法A聲明throws出Exception,調用方法A的代碼必須try...catch...該異常,如果:
(1)有匹配異常的catch塊,則優先走匹配異常的catch塊
(2)如果沒有匹配異常的catch塊,但是有catch (Exception e){...},則走catch (Exception e){...}
(3)如果沒有沒有匹配的catch塊,則調用方法A的地方throw異常,方法終止
7、如果方法中沒有try...catch異常而該方法發生了異常,且方法聲明中有throws,那么發生的該異常可以被拋到調用方法的代碼中
8、如果方法中對某個代碼塊做了try...catch並且想把catch到的異常拋給調用方法的地方,那么:
(1)不可以只有throw沒有throws,這將會導致捕獲到的異常無法被拋給調用方法的地方
(2)不可以只有throws沒有throw,這將會導致編譯出錯
只有在catch塊中throw捕獲到的異常並且在方法聲明的地方throws異常,才可以將一個異常正確地拋給調用方法的地方