1. 程式人生 > >通俗程式設計——白話JAVA異常機制

通俗程式設計——白話JAVA異常機制

任何程式都追求正確有效的執行,除了保證我們程式碼儘可能的少出錯之外,我們還要考慮如何有效的處理異常,一個良好的異常框架對於系統來說是至關重要的。最近在給公司寫採集框架的時候系統的瞭解一邊,收穫頗多,特此記錄相關的理論。

1 .異常體系簡介:

異常是指由於各種不期而至的情況,導致程式中斷執行的一種指令流,如:檔案找不到、非法引數、網路超時等。為了保證正序正常執行,在設計程式時必須考慮到各種異常情況,並正確的對異常進行處理。異常也是一種物件,java當中定義了許多異常類,並且定義了基類java.lang.Throwable作為所有異常的超類。Java語言設計者將異常劃分為兩類:ErrorException

,其體系結構大致如下圖所示:

Throwable:有兩個重要的子類:Exception(異常)和Error(錯誤),兩者都包含了大量的異常處理類。

1Error(錯誤):是程式中無法處理的錯誤,表示執行應用程式中出現了嚴重的錯誤。此類錯誤一般表示程式碼執行時JVM出現問題。通常有Virtual MachineError(虛擬機器執行錯誤)、NoClassDefFoundError(類定義錯誤)等。比如說當jvm耗完可用記憶體時,將出現OutOfMemoryError。此類錯誤發生時,JVM將終止執行緒。

這些錯誤是不可查的,非程式碼性錯誤。因此,當此類錯誤發生時,應用不應該去處理此類錯誤。

2Exception(異常):程式本身可以捕獲並且可以處理的異常。

Exception這種異常又分為兩類:執行時異常和編譯異常。

1、執行時異常(不受檢異常)RuntimeException類極其子類表示JVM在執行期間可能出現的錯誤。比如說試圖使用空值物件的引用(NullPointerException)、陣列下標越界(ArrayIndexOutBoundException)。此類異常屬於不可查異常,一般是由程式邏輯錯誤引起的,在程式中可以選擇捕獲處理,也可以不處理。

2、編譯異常(受檢異常)Exception中除RuntimeException極其子類之外的異常。如果程式中出現此類異常,比如說IOException

,必須對該異常進行處理,否則編譯不通過。在程式中,通常不會自定義該類異常,而是直接使用系統提供的異常類。

可查異常與不可查異常:java的所有異常可以分為可查異常(checked exception)和不可查異常(unchecked exception)。

1、可查異常編譯器要求必須處理的異常。正確的程式在執行過程中,經常容易出現的、符合預期的異常情況。一旦發生此類異常,就必須採用某種方式進行處理。除RuntimeException及其子類外,其他的Exception異常都屬於可查異常。編譯器會檢查此類異常,也就是說當編譯器檢查到應用中的某處可能會此類異常時,將會提示你處理本異常——要麼使用try-catch捕獲,要麼使用throws語句丟擲,否則編譯不通過。

2、不可查異常:編譯器不會進行檢查並且不要求必須處理的異常,也就說當程式中出現此類異常時,即使我們沒有try-catch捕獲它,也沒有使用throws丟擲該異常,編譯也會正常通過。該類異常包括執行時異常(RuntimeException極其子類)和錯誤(Error)。

2.異常處理流程:

java應用中,異常的處理機制分為丟擲異常和捕獲異常。

丟擲異常:當一個方法出現錯誤而引發異常時,該方法會將該異常型別以及異常出現時的程式狀態資訊封裝為異常物件,並交給本應用。執行時,該應用將尋找處理異常的程式碼並執行。任何程式碼都可以通過throw關鍵詞丟擲異常,比如java原始碼丟擲異常、自己編寫的程式碼丟擲異常等。

捕獲異常:一旦方法丟擲異常,系統自動根據該異常物件尋找合適異常處理器(Exception Handler)來處理該異常。所謂合適型別的異常處理器指的是異常物件型別和異常處理器型別一致。

對於不同的異常,java採用不同的異常處理方式:

1、執行異常將由系統自動丟擲,應用本身可以選擇處理或者忽略該異常。

2對於方法中產生的Error,該異常一旦發生JVM將自行處理該異常,因此java允許應用不丟擲此類異常。

3、對於所有的可查異常,必須進行捕獲或者丟擲該方法之外交給上層處理。也就是當一個方法存在異常時,要麼使用try-catch捕獲,要麼使用該方法使用throws將該異常拋呼叫該方法的上層呼叫者。

2.1捕獲異常

1try-catch語句

try {

//可能產生的異常的程式碼區,也成為監控區

}catch (ExceptionType1 e) {

//捕獲並處理try丟擲異常型別為ExceptionType1的異常

}catch(ExceptionType2 e) {

//捕獲並處理try丟擲異常型別為ExceptionType2的異常

}

監控區一旦發生異常,則會根據當前執行時的資訊建立異常物件,並將該異常物件丟擲監控區,同時

系統根據該異常物件依次匹配catch子句,若匹配成功(丟擲的異常物件的型別和catch子句的異常類的型別或者是該異常類的子類的型別一致),則執行其中catch程式碼塊中的異常處理程式碼,一旦處理結束,那就意味著整個try-catch結束。含有多個catch子句,一旦其中一個catch子句與丟擲的異常物件型別一致時,其他catch子句將不再有匹配異常物件的機會。

2try-catch-finally

try {

//可能產生的異常的程式碼區

}catch (ExceptionType1 e) {

//捕獲並處理try丟擲異常型別為ExceptionType1的異常

}catch (ExceptionType2 e){

//捕獲並處理try丟擲異常型別為ExceptionType2的異常

}finally{

//無論是出現異常,finally塊中的程式碼都將被執行

}

3try-catch-finally程式碼塊的執行順序:

A)try沒有捕獲異常時,try程式碼塊中的語句依次被執行,跳過catch。如果存在finally則執行finally程式碼塊,否則執行後續程式碼。

Btry捕獲到異常時,如果沒有與之匹配的catch子句,則該異常交給JVM處理。如果存在finally,則其中的程式碼仍然被執行,但是finally之後的程式碼不會被執行。

Ctry捕獲到異常時,如果存在與之匹配的catch,則跳到該catch程式碼塊執行處理。如果存在finally則執行finally程式碼塊,執行完finally程式碼塊之後繼續執行後續程式碼;否則直接執行後續程式碼。另外注意,try程式碼塊出現異常之後的程式碼不會被執行。(見下圖:)

4、總結

try程式碼塊:用於捕獲異常。其後可以接零個或者多個catch塊。如果沒有catch塊,後必須跟finally塊,來完成資源釋放等操作,另外建議不要在finally中使用return,不用嘗試通過catch來控制程式碼流程。

catch程式碼塊:用於捕獲異常,並在其中處理異常。

finally程式碼塊:無論是否捕獲異常,finally程式碼總會被執行。如果try程式碼塊或者catch程式碼塊中有return語句時,finally程式碼塊將在方法返回前被執行。注意以下幾種情況,finally程式碼塊不會被執行:

1、在前邊的程式碼中使用System.exit()退出應用。

2、程式所在的執行緒死亡或者cpu關閉

3、如果在finally程式碼塊中的操作又產生異常,則該finally程式碼塊不能完全執行結束,同時該異常會覆蓋前邊丟擲的異常。

2.2丟擲異常

1throws丟擲異常

如果一個方法可能丟擲異常,但是沒有能力處理該異常或者需要通過該異常向上層彙報處理結果,可以在方法宣告時使用throws來丟擲異常。這就相當於計算機硬體發生損壞,但是計算機本身無法處理,就將該異常交給維修人員來處理。

publicmethodName throws Exception1,Exception2….(params){}

其中Exception1,Exception2…為異常列表一旦該方法中某行程式碼丟擲異常,則該異常將由呼叫該方法的上層方法處理。如果上層方法無法處理,可以繼續將該異常向上層拋。

2throw丟擲異常

在方法內,用throw來丟擲一個Throwable型別的異常。一旦遇到到throw語句,後面的程式碼將不被執行。然後,便是進行異常處理——包含該異常的try-catch最終處理,也可以向上層丟擲。注意我們只能丟擲Throwable類和其子類的物件。

throw newExceptionType;

比如我們可以丟擲:throw new Exception();

也有時候我們也需要在catch中丟擲異常,這也是允許的,比如說:

Try{

//可能會發生異常的程式碼

}catch(Exceptione){

throw newException(e);

}

3、異常關係鏈

在實際開發過程中經常在捕獲一個異常之後丟擲另外一個異常,並且我們希望在新的異常物件中儲存原始異常物件的資訊,實際上就是異常傳遞,即把底層的異常物件傳給上層,一級一級,逐層丟擲。當程式捕獲了一個底層的異常,而在catch處理異常的時候選擇將該異常拋給上層這樣異常的原因就會逐層傳遞,形成一個由低到高的異常鏈。但是異常鏈在實際應用中一般不建議使用,同時異常鏈每次都需要就將原始的異常物件封裝為新的異常物件,消耗大量資源。現在(jdk 1.4之後)所有的Throwable的子類構造中都可以接受一個cause物件,這個cause也就是原始的異常物件。

下面是一個不錯的例子:

/*

*高層異常

*/

classHighLevelExceptionextends Exception{

public HighLevelException(Throwable cause) {

super(cause);

}

}

/*

*中層異常

*/

classMiddleLevelExceptionextends Exception{

public MiddleLevelException(Throwable cause) {

super(cause);

}

}

/*

*底層異常

*/

classLowLevelExceptionextends Exception{

}

publicclass TestException {

publicvoid highLevelAccess()throws HighLevelException{

try {

middleLevelAccess();

}catch (Exception e) {

thrownew HighLevelException(e);

}

}

publicvoid middleLevelAccess()throws MiddleLevelException{

try {

lowLevelAccess();

}catch (Exception e) {

thrownew MiddleLevelException(e);

}

}

publicvoid lowLevelAccess()throws LowLevelException {

thrownew LowLevelException();

}

publicstaticvoid main(String[] args) {

/*

* lowlevelAccess()將異常物件拋給middleLevelAccess(),而

* middleLevelAccess()又將異常物件拋給highLevelAccess(),

*也就是底層的異常物件一層層傳遞給高層。最終在在高層可以獲得底層的異常物件。

*/

try {

new TestException().highLevelAccess();

}catch (HighLevelException e) {

e.printStackTrace();

System.out.println(e.getCause());

}

}

}

4、異常轉譯

異常轉義就是將一種型別的異常轉成另一種型別的異常,然後再丟擲異常。之所以要進行轉譯,是為了更準確的描述異常。就我個人而言,我更喜歡稱之為異常型別轉換。在實際應用中,為了構建自己的日誌系統,經常需要把系統的一些異常資訊描述成我們想要的異常資訊,就可以使用異常轉譯。異常轉譯針對所有Throwable類的子類而言,其子型別都可以相互轉換。

通常而言,更為合理的轉換方式是:

1、Error——>Exception

2、Error——>RuntimeException

3、Exception——>RuntimeException,

在下面的程式碼中,我們自定義了MyException異常類,然後我們將IOException型別的異常轉為MyException型別的異常,最後丟擲。

class MyExceptionextends Exception {

public MyException(String msg, Throwable e) {

super(msg, e);

}

}

publicclass Demo {

publicstaticvoid main(String[] args)throws MyException {

Filefile =new File("H:/test.txt");

if (file.exists())

try {

file.createNewFile();

}catch (IOException e) {

thrownew MyException("檔案建立失敗!", e);

}

}

}

5Throwable類中常用的方法

catch(Exception e)中的Exception就是異常的變數型別,e則是形參。通常在進行異常輸出時有如下幾個方法可用:

e.getCause():返回丟擲異常的原因。

e.getMessage():返回異常資訊。

e.printStackTrace():發生異常時,跟蹤堆疊資訊並輸出。

6、 常異總結

此部分可以api文件中進行查閱,這裡僅做參考。

常見異常:

java.lang.IllegalAccessError:違法訪問錯誤。當一個應用試圖訪問、修改某個類的域(Field)或者呼叫其方法,但是又違反域或方法的可見性宣告,則丟擲該異常。

java.lang.InstantiationError:例項化錯誤。當一個應用試圖通過Javanew操作符構造一個抽象類或者介面時丟擲該異常.

java.lang.OutOfMemoryError:記憶體不足錯誤。當可用記憶體不足以讓Java虛擬機器分配給一個物件時丟擲該錯誤。
java.lang.StackOverflowError
:堆疊溢位錯誤。當一個應用遞迴呼叫的層次太深而導致堆疊溢位或者陷入死迴圈時丟擲該錯誤。

java.lang.ClassCastException:類造型異常。假設有類ABA不是B的父類或子類),OA的例項,那麼當強制將O構造為類B的例項時丟擲該異常。該異常經常被稱為強制型別轉換異常。
java.lang.ClassNotFoundException
:找不到類異常。當應用試圖根據字串形式的類名構造類,而在遍歷CLASSPAH之後找不到對應名稱的class檔案時,丟擲該異常。

java.lang.ArithmeticException:算術條件異常。譬如:整數除零等。
java.lang.ArrayIndexOutOfBoundsException
:陣列索引越界異常。當對陣列的索引值為負數或大於等於陣列大小時丟擲。

java.lang.IndexOutOfBoundsException:索引越界異常。當訪問某個