Java異常體系和異常處理機制


異常簡介

在程序運行過程中出現錯誤,導致程序出現非預期場景。異常處理可以保證出現錯誤后,控制接下來的程序流程,是選擇定位錯誤信息,還是拋出異常或捕獲異常、還是避免程序非正常退出,都取決於我們。

Java的異常體系結構(來自網絡)

   

Java把異常作為一種類,當做對象來處理。所有異常類的基類是Throwable類,兩大子類分別是Error和Exception。這些異常類可以分為三種類型:系統錯誤、異常和運行時異常。系統錯誤由Java虛擬機拋出,用Error類表示。Error類描述的是內部系統錯誤,例如Java虛擬機崩潰。這種情況僅憑程序自身是無法處理的,在程序中也不會對Error異常進行捕捉和拋出。

異常(Exception)又分為RuntimeException(運行時異常)和CheckedException(檢查異常),兩者區別如下:

  • RuntimeException:程序運行過程中出現錯誤,才會被檢查的異常。例如:類型錯誤轉換,數組下標訪問越界,空指針異常、找不到指定類等等。
  • CheckedException:來自於Exception且非運行時異常都是檢查異常,編譯器會強制檢查並通過try-catch塊來對其捕獲,或者在方法頭聲明該異常,交給調用者處理。

兩種異常的處理方式:若是運行時異常,則表明程序出錯,應該找到錯誤並修改,而不是對其捕獲。若是檢查異常,遵循該原則:誰知情誰處理,誰負責誰處理,誰導致誰處理。處理就是對其捕獲並處理。

 

深入了解異常處理

異常處理的5個關鍵字:try、catch、throw、throws和finally關於它們的用法和注意點,會在下面一一介紹。

Java的異常處理模型基於三種操作: 聲明異常、拋出一個異常和捕獲一個異常。

聲明異常(throws)

                                                            //不捕獲,而是聲明該異常,交給調用者處理
   public static void method() {                           public static void method2() throws Exception {
       /*try-catch塊捕獲異常*/                                     if(5 > 3) {            
       if(5 > 3) {                                                   throw new Exception(); //拋出異常        
           try {                                                   }        
               throw new Exception(); //拋出異常             }
           } catch (Exception e) {
               e.printStackTrace();//捕獲異常后的處理
           }
       }
   }

在Java中,當前執行的語句必屬於某個方法,Java解釋器調用main方法執行開始執行程序。若方法中存在檢查異常,如果不對其捕獲,那必須在方法頭中顯式聲明該異常,以便於告知方法調用者此方法有異常,需要進行處理。 在方法中聲明一個異常,方法頭中使用關鍵字throws,后面接上要聲明的異常。若聲明多個異常,則使用逗號分割。如下所示:

    public static void method() throws IOException, FileNotFoundException{
        //something statements
    }

【注意】若是父類的方法沒有聲明異常,則子類繼承方法后,也不能聲明異常。

 

拋出異常(throw)

如果代碼可能會引發某種錯誤,可以創建一個合適的異常類實例並拋出它,這就是拋出異常。如下所示:

    public static double method(int value) {
        if(value == 0) {
            throw new ArithmeticException("參數不能為0"); //拋出一個運行時異常
        }
        return 5.0 / value;
    }

大部分情況下都不需要手動拋出異常,因為Java的大部分方法要么已經處理異常,要么已聲明異常。所以一般都是捕獲異常或者再往上拋。

捕獲異常(try-catch)

當拋出一個異常時,可以在try-catch塊中捕獲它並進行處理。

        try {
            //包含可能會出現異常的代碼以及聲明異常的方法
        } catch (ClassCastException e) {
            //捕獲指定異常並進行處理
        }catch(Exception ex) {
            //捕獲指定異常並進行處理
        }

若執行try塊的過程中沒有發生異常,則跳過catch子句。若是出現異常,try塊中剩余語句不再執行。開始逐步檢查catch塊,判斷catch塊的異常類實例是否是捕獲的異常類型。匹配后執行相應的catch塊中的代碼。如果異常沒有在當前的方法中被捕獲,就會被傳遞給該方法的調用者。這個過程一直重復,直到異常被捕獲或被傳給main方法(交給JVM來捕獲)。

catch捕獲異常的順序

一個通用父類可以派生出各種異常類,如果一個catch塊可以捕獲一個父類的異常對象,它就能捕獲那個父類的所有子類的異常對象。如果捕獲的是多個同類型異常,則子類異常在前,父類異常在后,不然會導致編譯錯誤。這是因為父類異常囊括了子類異常,如果父類異常在前,子類異常永遠捕獲不到,導致有時候無法准確描述錯誤信息。

        try {
            File f =new File("C:\\ProgramFile\\test.txt");
            FileInputStream fis = new FileInputStream(f);
        } catch (FileNotFoundException e) { //子類異常
            e.printStackTrace();
        } catch(IOException ie) {  //父類異常
            ie.printStackTrace();
        } catch(Exception e) { //基類運行時異常
            e.printStackTrace();
        }

這里只是用於演示catch塊捕獲的順序。捕獲多個異常時,可以使用catch(Exception1 | Exception2| Exception3)的形式來優化捕獲異常的代碼結構。

 

將聲明異常、拋出異常和捕獲異常綜合在一起。演示如下:

    public static void main(String[] args) {
        for(int i = 2; i < 100; i++) {
            //對運行時異常,可以選擇捕獲也可以不選擇捕獲
            if(isPrime(i)) {
                System.out.print(i + " ");
            }
        }
    }        //檢測是否為質數
    public static boolean isPrime(int num) throws IllegalArgumentException{
        //拋出一個運行時異常
        if(num < 2) throw new IllegalArgumentException("質數不能小於2");
        for(int i = 2; i < num; i++) {
            if(num % i == 0) {//若能被1和本身之外的數整除,則非質數
                return false;
            }
        }
        return true;
    }

因為拋出的是運行時異常,可以選擇捕獲或者不捕獲。但如果拋出檢查異常,在編譯時就必須選擇捕獲或者聲明。

 

finally語句塊

無論是否有異常,finally塊中的代碼總是會被執行的。 finally語句在執行關閉資源的語句時非常有用。

        //第一種形式                                                      //第二種形式                            
        try {                                                            try {
            //執行程序代碼,可能會出現異常                                     //執行程序代碼,可能會出現異常                                        
        }catch(Exception e) {                                            }finally {    
            //捕獲異常並處理                                                 //必執行的代碼    
        }finally {                                                       }
            //必執行的代碼
        }

try-catch-finally的執行流程

try塊中引起異常,異常代碼之后的語句不再執行,若被catch塊捕獲,執行匹配的catch塊,然后執行finally語句。若catch塊不能捕獲異常,則執行finally語句,之后將異常傳遞給這個方法的調用者。

        Scanner sc = new Scanner(System.in);
        int a = 0; //保證局部變量a在各個塊中可用
        try {
            a = sc.nextInt();
            if(a < 0) throw new IllegalArgumentException();
            System.out.println("執行完try語句。a:" + a);
        }catch(IllegalArgumentException e){
            System.out.println("執行catch語句");
            System.out.println("數值小於0,不符合。已設為默認值 1");
            a = 1;
        }finally {
            System.out.println("執行finally語句。a:" + a);
        }
    //未引發異常                                      //引發異常並捕獲
    5                                                -5
    執行完try語句。a:5                                執行catch語句
    執行finally語句。a:5                              數值小於0,不符合。已設為默認值 1
                                                    執行finally語句。a:1

try-finally的執行流程

try塊中引起異常,異常代碼之后的語句不再執行,直接執行finally語句。 try塊沒有引發異常,則執行完try塊就執行finally語句。

try-finally可用在不需要捕獲異常的代碼,可以保證資源在使用后被關閉。例如IO流中執行完相應操作后,關閉相應資源;使用Lock對象保證線程同步,通過finally可以保證鎖會被釋放;數據庫連接代碼時,關閉連接操作等等。

        //以Lock加鎖為例,演示try-finally
     ReentrantLock lock = new ReentrantLock(); try { //需要加鎖的代碼 }finally { lock.unlock(); //保證鎖一定被釋放 }
finally遇見如下情況不會執行
  • 在前面的代碼中用了System.exit()退出程序。
  • finally語句塊中發生了異常。
  • 程序所在的線程死亡。
  • 關閉CPU。

 

try、catch、finally、throw和throws使用歸納

  • try、catch和finally都不能單獨使用,只能是try-catch、try-finally或者try-catch-finally。
  • try語句塊監控代碼,出現異常就停止執行下面的代碼,然后將異常移交給catch語句塊來處理。
  • finally語句塊中的代碼一定會被執行,常用於回收資源 。
  • throws:聲明一個異常,告知方法調用者。
  • throw :拋出一個異常,至於該異常被捕獲還是繼續拋出都與它無關。
 
Java編程思想一書中,對異常的總結。
  1. 在恰當的級別處理問題。(在知道該如何處理的情況下了捕獲異常。)
  2. 解決問題並且重新調用產生異常的方法。
  3. 進行少許修補,然后繞過異常發生的地方繼續執行。
  4. 用別的數據進行計算,以代替方法預計會返回的值。
  5. 把當前運行環境下能做的事盡量做完,然后把相同的異常重拋到更高層。
  6. 把當前運行環境下能做的事盡量做完,然后把不同的異常拋到更高層。
  7. 終止程序。
  8. 進行簡化(如果你的異常模式使問題變得太復雜,那么用起來會非常痛苦)。
  9. 讓類庫和程序更安全。

 

自定義異常

通過繼承Exception類來定義一個異常類。Java已經提供了很多異常類,盡量使用這些異常類而不要創建自己的異常類。除非Java的異常類不能很好地描述問題時,才自定義異常來進行准確描述。對於自定義異常,不需要太多功能,類名能准確描述問題是關鍵。

自定義異常如下所示:

//判斷長度是否合法的自定義異常
public class LengthException extends Exception {
    public LengthException() {}
    public LengthException(String s) {
        super(s);
    }
    @Override
    public String getMessage() {
        return super.getMessage();
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM