1. 程式人生 > 實用技巧 >【JavaSE學習筆記04】異常

【JavaSE學習筆記04】異常

Chapter 9 異常

異常:指程式在執行過程中,出現的非正常的情況,最終導致JVM非正常停止。

在Java等面向物件的程式語言中,異常是一個,所有異常都是發生在執行階段的(因為也只有程式執行階段方可new 物件),產生異常其實就是建立異常物件。而Java處理異常的方式為中斷處理。

9.1 異常體系

如下的UML圖關於異常的繼承結構,異常的根類為java.lang.Throwable(顧名思義,所有異常均可丟擲),其下有兩個子類:java.lang.Errorjava.lang.Exception。而我們平常所說的異常指的是後者。

  • 錯誤(Error):Java程式執行過程中若發生錯誤,則無法恢復,只能夠退出。
  • 編譯時異常(CheckException,或稱受控異常、檢查異常):出現了這種型別的異常必須顯式地處理,若不處理則Java程式將無法編譯通過。
  • 執行時異常(RuntimeException,或稱UncheckedException,未檢查異常,非受控異常):此種異常發生機率低,可以不用顯式地處理(eg: 算術異常),也能夠編譯通過。

異常資訊的獲取:

Throwable類中定義了一些檢視方法:

  • public String getMessage():獲取異常的描述資訊與原因,多用於向用戶提示錯誤的原因。

  • public void printStackTrace():列印異常的跟蹤棧資訊並輸出至控制檯。(開發與除錯階段常用)

9.2 異常處理機制

Java異常的作用是增強程式健壯性。

9.2.1 宣告異常throws

Java的一個方法不僅需要告訴編譯器將要返回什麼值,還需要告訴編譯器有可能發生什麼錯誤。

一個方法必須宣告所有可能丟擲的已檢查異常(CheckedException),而未檢查異常要麼是不可控制的Error,要麼是應該避免發生的未檢查異常(RuntimeException)

故,方法應在其首部throws宣告可能發生的異常(但不一定會發生),其格式為:

修飾符 返回值型別 方法名(引數) throws 異常類名1, 異常類名2 ...{ }

  • 例如:public FileInputStream(String name) throws FileNotFoundException

    這個宣告表示該構造器由String引數產生一個FileInputStream物件,但也有可能丟擲一個FileNotFoundException類物件。若該方法真的丟擲了這樣一個異常物件,執行時系統就會開始搜尋異常處理器,以便知道如何處理FileNotFoundException物件。

在Java中,沒有throws說明符的方法將不能丟擲任何已檢查異常,它是可以單獨使用的。一旦對該方法進行throws宣告,必須交由呼叫該方法的上一級方法的語句來處理(捕獲 或 繼續宣告),若呼叫者不進行處理,編譯一般不能通過!

執行時異常被丟擲可以不處理,即允許不捕獲也不宣告丟擲。

注意:Java中異常發生後若一直往上拋,最終拋給main方法,main方法繼續向上拋,拋給呼叫者JVM,JVM知道這個異常發生後會終止Java程式執行。

9.2.2 丟擲異常throw

在Java中,提供throw關鍵字,用在方法內,丟擲一個異常物件,將該異常物件傳遞到呼叫者處,並直接結束當前方法的執行。

格式為throw new 異常類名(引數); 或者 異常類 異常物件名 = new 異常類名(引數); throw 異常物件名

String readData(Scanner in) throws EOFException{ //宣告可能出現的異常
    //...
    while(...){
        if(!in.hasNext()){
            ...
            if(n < len) throw new EOFException(); //建立異常物件,並將其丟擲
        }
    }
    return s;
}

丟擲異常的4種情況:

  1. 呼叫一個丟擲已檢查異常的方法,例如,FileInputStream 構造器。
  2. 程式執行過程中發現錯誤,並且利用 throw 語句丟擲一個已檢查異常。
  3. 程式出現錯誤。例如,a[-1] = 0;
  4. Java虛擬機器和執行時庫出現的內部異常。

一般而言,throw語句不能單獨使用,它應與throws宣告配套使用。

我們一般是使用一次捕獲 多次處理方式,格式如下:

try{
    //編寫可能會出現異常的程式碼
} catch(異常型別A e){
    //處理A型別異常
} catch(異常型別B e){
    //處理B型別異常
}

注意,這種異常處理方式,要求多個catch中異常不能相同,且catch中的多個異常之間有子父類異常的關係,那麼子類異常要求在上面的catch處理,父類異常在下面的catch處理。

9.2.3 捕獲異常try-catch

捕獲異常:Java中對異常有針對性的語句進行捕獲,可以對出現的異常進行制定方式的處理。要想捕獲一個異常,必須設定try...catch語句塊。語法格式如下:

try{
    //編寫可能產生異常的程式碼。(一般呼叫方法,該方法聲明瞭異常並能夠對異常丟擲)
} catch(異常型別 異常名){
    //處理異常的程式碼:記錄日誌/列印異常資訊/繼續丟擲異常
}

如果在try語句塊中任何程式碼丟擲一個在catch子句中說明的異常類,那麼:

  1. 程式將跳過try語句塊的其餘程式碼;
  2. 程式將執行catch子句中的處理器程式碼。

舉例如下:

public class JustTest {
    public static void main(String[] args) {
        try{ 
            read(b.txt);
        } catch(FileNotFoundException e){ 
            System.out.println(e); //try中丟擲的是什麼異常,在括號中就定義什麼異常型別
        }
        System.out.println("Over!");;
    }
    public static void read(String path) throws FileNotFoundException{
        if(!path.equals("a.txt")){
            throw new FileNotFoundException("檔案不存在");
        }
    }
}

9.2.4 finally 程式碼塊

當代碼丟擲一個異常時,就會終止方法中剩餘程式碼的處理,並退出這個方法的執行。當我們在try語句塊中開啟一些物理資源(磁碟檔案/網路連線/資料庫連線等),我們需要在使用完之後關閉已開啟的資源。注意,finally不能單獨使用。

public class JustTest {
    public static void main(String[] args) {
        try{
            read(b.txt);
        } catch(FileNotFoundException e){
            System.out.println(e); //try中丟擲的是什麼異常,在括號中就定義什麼異常型別
        } finally {
            System.out.println("不管程式如何,我會被執行的~");
        }
        System.out.println("Over!");;
    }
    public static void read(String path) throws FileNotFoundException{
        if(!path.equals("a.txt")){
            throw new FileNotFoundException("檔案不存在");
        }
    }
}

finally子句中的程式碼是一定會執行的!如果try語塊有返回值,finally子句也會執行完再最後返回(除非使用System.exit(0);則會中斷finally子句的執行)

public class JustTest {
    public static void main(String[] args) {
        int f = Judge();
        System.out.println(f);
    }
    public static int Judge() {
        try{
            System.out.println("11111");
            return 0;
        } finally {
            System.out.println("233333");
        }
    }
}

finally語句塊設計的目的僅是為了讓方法執行一些重要的收尾工作,而不是用來計算返回值的,故在finally語句塊中修改返回值是無效的!同時不建議在finally語句塊中返回一些值。

關於finalfinallyfinalize的區別

  • final 屬於關鍵字,其修飾的類無法繼承、修飾的方法無法覆蓋、修飾的變數不能重新賦值。
  • finally 屬於關鍵字,與try...catch聯合使用,finally語句塊中的程式碼是必須執行的。
  • finalize 識別符號,是一個Object類中的方法名,該方法是由垃圾回收器GC負責呼叫。

9.3 自定義異常

在實際開發中總是有些異常情況是SUN沒有定義的,根據自己業務的異常情況來定義異常類。

自定義異常類的方式:

  • 自定義一個編譯時異常類:自定義類,並繼承於java.lang.Exception
  • 自定義一個執行時期的異常類:自定義類,並繼承於java.lang.RuntimeException

習慣上,定義的類應包含兩個構造器,一個是預設無參構造器,另一個是帶有詳細描述資訊的構造器。

public class RegisterException extends Exception{
    public RegisterException(){ //無參構造
    }
    public RegisterException(String message){
        super(message); //超類Throwable的toString方法將會打印出這些詳細資訊,除錯中有用。
    }
}