1. 程式人生 > >Java基礎:深入理解java異常處理機制的原理和開發應用【轉載】

Java基礎:深入理解java異常處理機制的原理和開發應用【轉載】

Java異常處理機制在日常開發中應用頻繁,本篇文章主要在基礎的使用方法上,更進一步的,如何更加合理的使用異常機制,希望可以對各位朋友能有所幫助。

  Java異常處理機制其最主要的幾個關鍵字:try、catch、finally、throw、throws,以及各種各樣的Exception。本篇文章主要在基礎的使用方法上,介紹瞭如何更加合理的使用異常機制。 

try-catch-finally

    try-catch-finally塊的用法比較簡單,使用頻次也最高。try塊中包含可能出現異常的語句(當然這是人為決定的,try理論上可以包含任何程式碼),catch塊負責捕獲可能出現的異常,finally負責執行必須執行的語句,這裡的程式碼不論是否發生了異常,都會被執行。

    針對這部分,因為很基礎,所以就提幾點比較關鍵的建議:

      1、當你在寫try-catch語句的時候,腦子裡是知道自己要去針對哪種異常進行處理的,不要只是以防萬一,加了個catch(Exception e),這是毫無意義的。並且,一個try塊中可能有多個異常,對於每一類異常,要分別寫一個catch進行捕獲。       

      2、針對可能出現異常的語句進行try-catch,大段程式碼的try-catch會非常不利於維護程式碼時定位異常可能發生的位置,對於肯定不會發生異常的穩定的程式碼,不需要放在try塊中。

      3、try-catch雖然在功能上,可以成為流程控制的工具,達到條件分支的效果。但相比於if-else語句,java的異常處理機制基於面向物件的思想,使用過程中需要更多的時間與空間的開銷,所以不要用異常機制去做基本的條件判斷,只有在程式會因為異常而中斷時進行捕獲和處理。

      4、finally塊中永遠不要寫return語句,因為finally塊中總是最後執行,他會改變預期在trycatch塊中的返回值(舉個例子,你在catch中捕獲了一個異常並丟擲e,又在finally語句中return true,這樣你丟擲的異常就"消失"了,因為當前函式的執行結果已經從丟擲異常 轉變成 return true)。另外,在使用資源物件與流物件時,finally塊必須對資源物件、流物件進行關閉。

Java異常體系結構

    Java異常體系的基類是Throwable,它主要有兩個子類:Error 和 Exception。其結構如下圖:

      

    上圖中,Error是指程式無法處理的錯誤,多指系統內部比較嚴重的錯誤。大多數這類錯誤與開發人員無關,我們關注的主要是Exception。

    Exception主要分為兩塊:執行時異常非執行時異常。RuntimeException及其子類都稱為執行時異常;除此之外, 所有Exception的子類異常都是非執行時異常。

    執行時異常多指程式邏輯上出現問題(也就是我們自己寫程式碼邏輯出了問題),常見的錯誤包括 ClassCastException:型別轉換異常、NullPointerException:空指標異常、IndexOutOfBoundsException:越界異常...這些異常都可以通過程式邏輯處理來避免(比如加一個判斷語句判斷是否越界、是否屬於某型別、是否為null),所以編譯器把這些工作交給了程式設計師來把控,在編譯期即使手動丟擲了一個執行時異常不去捕獲,編譯器也會通過。因而這類異常也叫做"未檢查異常"(uncheck)。同樣屬於未檢查異常的還有所有的Error。即上圖中,所有藍色框表示未檢查異常,橙色框表示"檢查異常"(check)。對於檢查異常,在可能發生異常的位置需要用try-catch塊去捕獲並處理,如果不處理它,就會一直向上層呼叫丟擲,直到被處理為止。

throw 與 throws

    throws關鍵字主要在方法簽名中使用,用於宣告該方法可能丟擲的異常。throws 可以理解成是一種通知行為,沒有實際的丟擲異常的動作,而僅僅是告訴呼叫他的上層函式,這裡可能會丟擲這個異常;

    throw用於在函式體內語句中,表示丟擲一個實際的異常的實際動作,如果在函式內沒有捕獲並處理,那麼將會一直向上丟擲這個異常直到被main()/Thread.run()丟擲。

    當一個函式throws宣告函式可能丟擲一個非執行時異常(檢查異常)時,那麼即使這個函式內部不顯示使用throw,呼叫它的上層函式也必須包含處理這個異常的程式碼。舉個例子:

public class Main {

    public static void main(String[] args){

        exceptionTest();
    }
    static int exceptionTest() throws IOException {
        
        return 0;    
    }
} 

上述程式碼中呼叫的exceptionTest函式宣告丟擲一個IOException屬於檢查異常,哪怕exceptionTest函式中不可能丟擲這個異常,呼叫它的函式也必須對此異常做出捕獲處理。現在main函式中沒有相關的處理邏輯,所以會編譯錯誤,如下圖:

    而對執行時異常,就是另一種情況了:

public class Main {
 
 public static void main(String[] args){
 
 int i = divideTest(0);
 System.out.println(i);
 }
 static int divideTest(int b) throws ArithmeticException { 
 
 int i = 5/b;
 return i; 
 }
 }

    同樣在main函式沒有處理異常的邏輯,這次宣告丟擲的異常是ArithmeicException,他屬於執行時異常(RuntimeException),所以編譯器對宣告的丟擲置之不理:

 

  

  雖然編譯期通過,但在執行時程式仍然會自動丟擲執行時異常,並一直向上丟擲到Main函式。而Main()中沒有對該異常的捕獲處理,所以主執行緒終止。

    結論:我目前的理解是,throws一個執行時異常是沒有任何實際意義的,除非你為了遵循某個統一的規範而這樣做。throws 存在的意義主要是將可能的非執行時異常交給編譯器把關,讓編譯器監督開發人員對這些異常進行捕獲處理。

    另外,當你需要自定義一個異常時,如果需要在編譯期檢查,並在上層統一處理,那麼直接繼承Exception成為一個檢查異常;如果不需要編譯期檢查,丟擲異常表示程式異常需要直接中斷,那麼繼承RuntimeException成為一個執行時異常即可。

              希望本篇文章可以對各位朋友有所幫助!