1. 程式人生 > >Java-異常機制詳解以及開發時異常設計的原則要求

Java-異常機制詳解以及開發時異常設計的原則要求

異常處理的合理性完整性體現了一門語言是否成熟。而Java作為目前最流行的開發語言之一,固然具有一個完善的異常處理機制。本文將對異常處理機制來一次大的總結,後面講解一些原則性問題、開發技巧以及經常遇到的異常。

文章結構:1.異常機制樹的講解; 2.異常處理的五大關鍵字; 3.原則性問題以及開發使用異常技巧;4.異常鏈;5.Java的一些新特性;6.常見的異常類(這個查詢多個文章總結)

一、先來幅異常機制樹圖這裡寫圖片描述

在java中,所有的非正常情況都繼承Throwable。而Error和Exception是他的兩個子類,各自又含有一系列的子類,對應一系列的錯誤和異常。這裡可以看出java設計的特性與原則。

Error(錯誤):是程式無法處理的,指與虛擬機器相關的問題(比如:系統崩潰、虛擬機器錯誤)。這些錯誤無法恢復或者捕獲

Exception(異常):是程式本身可處理的。而異常又分為兩大類:Checked異常和Runtime異常(執行時異常)。

Checked異常是指可以在編譯階段內處理的異常,java認為必須顯示處理的異常。處理方式:1.當前方法明確如何處理該異常,就應該捕獲它,在對應的catch中修復 2.當前方法不知道如何處理該異常,應在定義該方法時宣告丟擲該異常。它的缺點:1.Java要求必須顯示捕獲並處理該異常,增加程式設計複雜度 2.在方法顯示宣告丟擲此異常,會導致方法簽名與異常耦合。它的優點
:可在編譯時提醒程式設計師程式碼可能存在的問題,提醒處理該異常。
Runtime異常是指編譯階段無須處理,debug過程處理的異常(因為java編譯器不會檢查它,可以通過編譯)。它的優勢:1.正常程式碼與錯誤處理程式碼的分離;2.保證程式健壯性;3.避免Checked異常的程式設計繁瑣性。
剩餘的異常均是這兩個的子類,這裡不過多描述,每個的異常是不同的,太多了。

二、異常處理的五大關鍵字:

java異常處理機制核心思想:丟擲異常,捕獲異常,處理異常

丟擲異常:當一個方法出現錯誤引發異常時,方法建立異常物件並交付執行時系統,異常物件中包含了異常型別和異常出現時的程式狀態等異常資訊。執行時系統負責尋找處置異常的程式碼並執行。
捕獲異常:在方法丟擲異常之後,執行時系統將轉為尋找合適的異常處理器(exception handler)。潛在的異常處理器是異常發生時依次存留在呼叫棧中的方法的集合。當異常處理器所能處理的異常型別與方法丟擲的異常型別相符時,即為合適 的異常處理器。執行時系統從發生異常的方法開始,依次回查呼叫棧中的方法,直至找到含有合適異常處理器的方法並執行。當執行時系統遍歷呼叫棧而未找到合適 的異常處理器,則執行時系統終止。同時,意味著Java程式的終止。
處理異常:在異常被捕獲到後,我們需要對可以處理的異常進行處理,在這一級的異常能處理的就在此處理,在這一級處理不了的就讓繼續往上拋異常,交由更上一級去處理。這就是開發的時候的異常處理邏輯樹。

五大關鍵字詳解:

try作用:裡面放置可能引發異常的程式碼;特點:在try裡面宣告的變數是程式碼塊內的區域性變數,只在try塊內有效,catch塊也不能訪問該變數。變數不是指物理資源

catch作用:用於處理某一型別的程式碼塊

finally:作用:用於回收在try塊開啟的物理資源,異常機制保證finally會被執行(物理資源:如資料庫連線、網路連線和磁碟檔案等); 特點:1.即使try和catch有return語句也會執行,但是如果try和catch有System.exit(1)語句來退出虛擬機器,就不會執行finally裡面的語句; 2.不要在其中使用return或throw等導致方法終止的語句,否則會導致很多奇怪的情況

Java垃圾回收機制不會回收任何物理資源,回收機制只回收堆記憶體中的物件所佔用的記憶體。所以要在finally去回收
//此處給出一例子
  try {  
    // 可能會發生異常的程式程式碼  
} catch (Type1 id1) {  
    // 捕獲並處理try丟擲的異常型別Type1  
} catch (Type2 id2) {  
    // 捕獲並處理try丟擲的異常型別Type2  
} finally {  
    // 無論是否發生異常,都將執行的語句塊  
}  
try-catch-finally執行順序:

1)當try沒有捕獲到異常時:try語句塊中的語句逐一被執行,程式將跳過catch語句塊,執行finally語句塊和其後的語句;

2)當try捕獲到異常,catch語句塊裡沒有處理此異常的情況:當try語句塊裡的某條語句出現異常時,而沒有處理此異常的catch語句塊時,此異常將會拋給JVM處理,finally語句塊裡的語句還是會被執行,但finally語句塊後的語句不會被執行;

3)當try捕獲到異常,catch語句塊裡有處理此異常的情況:在try語句塊中是按照順序來執行的,當執行到某一條語句出現異常時,程式將跳到catch語句塊,並與catch語句塊逐一匹配,找到與之對應的處理程式,其他的catch語句塊將不會被執行,而try語句塊中,出現異常之後的語句也不會被執行,catch語句塊執行完後,執行finally語句塊裡的語句,最後執行finally語句塊後的語句;

throw作用:用於丟擲一個實際異常,程式自行丟擲異常。可以單獨作為語句使用,丟擲一個具體的異常物件;注意:可以單獨使用,丟擲的不是異常類,而是一個異常例項,且每次只能丟擲一個異常例項!!是一個例項!。

throws作用:在方法簽名中使用,用於宣告該方法可能丟擲的異常;原理思路:當前方法不知道怎麼處理這一型別的異常,需要交由上一級呼叫者去處理;規則::子類方法宣告丟擲的異常型別應該是父類方法宣告丟擲的異常型別的子類或者與之相同。子類方法宣告丟擲的異常不允許比父類方法宣告丟擲的異常大。

//此處給出的例子包含throws、throw的運用,而且擴充套件了一些東西--自定義異常類(到了專案工程,為了異常層級清晰,層級傳遞,一般需要我們去自定義異常類來實現邏輯層層處理)
//1.分析過程:我們可以先看到我們自定義了一個普通的異常類,然後看到main方法裡面,該類物件使用了一個方法簽名中使用throws的方法,然後他進行了一系列的異常丟擲、捕獲,接著在異常處理丟擲我們的自定義異常類,然後列印異常棧資訊。
//2.一些註解:2.標識可能丟擲的異常: throws 異常類名1,異常類名2 
/*.捕獲異常: 
try{} 
catch(異常類名 y){} 
catch(異常類名 y){} 
.方法解釋 
getMessage() //輸出異常的資訊 
printStackTrace() //輸出導致異常更為詳細的資訊 
*/

public class AuctionTest
{
    private double initPrice = 30.0;
    // 因為該方法中顯式丟擲了AuctionException異常,
    // 所以此處需要宣告丟擲AuctionException異常
    public void bid(String bidPrice)
        throws AuctionException
    {
        double d = 0.0;
        try
        {
            d = Double.parseDouble(bidPrice);
        }
        catch (Exception e)
        {
            // 此處完成本方法中可以對異常執行的修復處理,
            // 此處僅僅是在控制檯列印異常跟蹤棧資訊。
            e.printStackTrace();
            // 再次丟擲自定義異常
            throw new AuctionException("競拍價必須是數值,"
                + "不能包含其他字元!");
        }
        if (initPrice > d)
        {
            throw new AuctionException("競拍價比起拍價低,"
                + "不允許競拍!");
        }
        initPrice = d;
    }
    public static void main(String[] args)
    {
        AuctionTest at = new AuctionTest();
        try
        {
            at.bid("df");
        }
        catch (AuctionException ae)
        {
            // 再次捕捉到bid方法中的異常。並對該異常進行處理
            System.err.println(ae.getMessage());
        }
    }
}
class AuctionException extends Exception { // 建立自定義異常類  
    String message; // 定義String型別變數  
    public AuctionException (String ErrorMessagr) { // 父類方法  
        message = ErrorMessagr;  
    }  

補充:自定義異常類的做法:

1.自定義異常: 

class 異常類名 extends Exception 
{ 
    public 異常類名(String msg) 
    { 
        super(msg); 
    } 
}  
或者:
class 異常類名 extends Exception 
{ 
    String message; // 定義String型別變數  
    public 異常類名(String ErrorMessagr) { // 父類方法  
        message = ErrorMessagr;  
    }  
    public String getMessage() { // 覆蓋getMessage()方法  
        return message;  
    }  
}  

使用:繼承異常父類實現子類,一般只需改變自定義異常的類名即可,讓該異常類的類名可準確描述該異常。

企業級應用異常處理:1.應用後臺需要通過日誌來記錄異常發生的詳細情況; 2.應用還需根據異常向應用使用者傳達某種提示

三、原則性問題以及開發使用異常技巧

原則性問題-異常處理機制初衷:1.將不可預期異常的處理程式碼和正常的業務邏輯處理程式碼分離。2.異常只應該用於處理非正常情況,不要用異常處理來代替正常流程控制。(對於完全可知的、處理方式清晰的錯誤,程式本應該提供相應的錯誤程式碼,而不是籠統稱為異常)3.先捕獲小異常,再捕獲大異常(Exception e 用此表示未知異常)4.對於完全已知的錯誤應該編寫處理這種錯誤的程式碼,增加程式健壯性。

開發使用異常技巧:

1.不要過度使用異常。原因:1.把異常和普通錯誤混淆在一起,不再編寫任何錯誤處理程式碼,而是以簡單地丟擲異常來代替所有的錯誤處理;2.使用異常處理來代替流程控制。
2.不要使用過於龐大的try塊。原因:1.程式碼塊太大,業務太複雜就會出現異常的可能性大大增加;2.try塊過大,就需要大量的catch來分析它們的邏輯,增加程式設計複雜度
3.避免使用Catch All語句。定義:Catch All指的是一種異常捕獲模組,可以處理程式發生的所有可能異常。 原因:1.所有異常都採用相同處理方式,會導致無法對不同的異常分情況處理,這時如果要分情況處理,則需要在catch使用分支語句(switch),這樣得不償失。2.這種捕獲可能將程式的錯誤、Runtime異常可能導致程式終止的情況都全部捕獲,從而“壓制”異常。如果出現“關鍵異常”也會被“悄悄”忽略
4.不要忽略捕獲到的異常。原因:已經捕獲到異常不處理或者僅僅列印跟蹤棧幾句話是於事無補的。做法:一、對於Cheacked異常,都應該儘量修復。二、當前環境能處理的儘量處理,然後進行異常轉譯,異常打包成當前層異常,重新拋給上層呼叫者。三、如果當前層不清楚如何處理,就不要用catch捕獲,直接用throws宣告丟擲該異常,讓上層呼叫者去處理。

四、異常鏈:

這裡寫圖片描述

企業應用做法:程式先捕獲原始異常,然後丟擲一個新的業務異常,新的業務異常中包含對使用者的提示資訊—–這種做法叫異常轉譯。因為核心是:在合適的層級處理異常。

異常的列印:Java的異常跟蹤棧,printSrackTrace()方法–用於列印異常的跟蹤棧資訊。這個設計體現了一個設計模式-職責鏈模式。這個異常傳播方式:只要異常沒被完全捕獲,異常從發生異常的方法逐漸向外傳播,首先傳給方法的呼叫者,該方法呼叫者再傳給其呼叫者,直至傳至上一層的方法,一層層傳遞。如果最上層的方法還沒處理該異常,JVM就會中止程式,並列印異常的跟蹤棧資訊。

五、Java的一些新特性方便了開發完善了邏輯

1.Java7的多異常捕獲–一個catch塊可捕獲多種型別的異常

2.Java7的自動關閉資源的try語句。定義:允許在try後面緊跟一堆圓括號,圓括號可以宣告、初始化多個物理資源,當try結束時自動關閉這些資源。實現基礎:那些資源實現類實現了AutoCloseable或Closeable介面。

3.SalException業務異常類。用來封裝原始異常,從而實現對異常的鏈式處理

六.常見的異常類:

(1). RuntimeException子類:

1、 java.lang.ArrayIndexOutOfBoundsException

陣列索引越界異常。當對陣列的索引值為負數或大於等於陣列大小時丟擲。

2、java.lang.ArithmeticException

算術條件異常。譬如:整數除零等。

3、java.lang.NullPointerException

空指標異常。當應用試圖在要求使用物件的地方使用了null時,丟擲該異常。譬如:呼叫null物件的例項方法、訪問null物件的屬性、計算null物件的長度、使用throw語句丟擲null等等

ClassCastException 搜尋- 型別強制轉換異常

IllegalArgumentException - 傳遞非法引數異常

4、java.lang.ClassNotFoundException

找不到類異常。當應用試圖根據字串形式的類名構造類,而在遍歷CLASSPAH之後找不到對應名稱的class檔案時,丟擲該異常。

5、java.lang.NegativeArraySizeException 陣列長度為負異常

6、java.lang.ArrayStoreException 陣列中包含不相容的值丟擲的異常

7、java.lang.SecurityException 安全性異常

8、java.lang.IllegalArgumentException 非法引數異常

(2).IOException

IOException:操作輸入流和輸出流時可能出現的異常。

EOFException 檔案已結束異常

FileNotFoundException 檔案未找到異常

(3). 其他

ClassCastException 型別轉換異常類

ArrayStoreException 陣列中包含不相容的值丟擲的異常

SQLException 操作資料庫異常類

NoSuchFieldException 欄位未找到異常

NoSuchMethodException 方法未找到丟擲的異常

NumberFormatException 字串轉換為數字丟擲的異常

StringIndexOutOfBoundsException 字串索引超出範圍丟擲的異常

IllegalAccessException 不允許訪問某類異常

InstantiationException 當應用程式試圖使用Class類中的newInstance()方法建立一個類的例項,而指定的類物件無法被例項化時,丟擲該異常

好了,Java-異常機制詳解以及開發注意點。歡迎在下面指出錯誤,共同學習!

轉載請註明:【JackFrost的部落格】

本博主最近會一直更新Java知識、資料結構與演算法、設計模式的文章(因為最近在學習,哈哈哈),歡迎關注。