Java源碼 - Exceotion異常類的基類Throwable分析

常用的異常有:Error、Exception,這兩個類的基類都是Throwable。

其中Error是用來表示編譯時和系統的錯誤,這一類問題,基本不需要我們關心。

Exception就是我們常見的異常。由源碼可知,Exception類自身並沒有什麼重要的東西,它只是Throwable類的一個子類。

public class Exception extends Throwable {
    static final long serialVersionUID = -3387516993124229948L;
    
    public Exception() {
        super();
    }
    public Exception(String message) {
        super(message);
    }
    public Exception(String message, Throwable cause) {
        super(message, cause);
    }
    public Exception(Throwable cause) {
        super(cause);
    }

    protected Exception(String message, Throwable cause,
                        boolean enableSuppression,
                        boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

因此我們學習異常的源碼重心就放在了Throwable類上。

一個異常類,主要的成員屬性有:異常名,以及描述信息,和異常的棧幀追蹤。

異常名:告訴我們是什麼異常,通常用類名錶示,比如IOException,這是IO異常。

描述信息:則是該異常的具體描述,是如何發生的,比如網絡連接失敗等等。

異常的棧幀追蹤:這記錄了異常發生的代碼位置。一般從main函數開始,記錄每一步函數調用,一直到最後導致異常的代碼。

public class Throwable implements Serializable {
    ...
     private String detailMessage;        /** 異常的具體細節*/

    /** 構造一個空的StackTrace數組 stackTrace  */
    private static final StackTraceElement[] UNASSIGNED_STACK = new StackTraceElement[0];
    private StackTraceElement[] stackTrace = UNASSIGNED_STACK;         /** 棧幀追蹤 */

    ...
    /** 字符串s則是異常名,輸出時直接輸出異常的類型。比如IOException*/
    public String toString() {
        String s = getClass().getName();
        String message = getLocalizedMessage();
        return (message != null) ? (s + ": " + message) : s;
    }
}

stackTrace,棧幀追蹤,是我們在debug時,經常需要獲取的信息。可以幫我們定位bug的位置。

那麼Throwable是如何操作該成員屬性的呢。(這學習源碼之前,我最好奇的地方,傻傻地猜想是用數組記錄了每一個方法調用,可是很顯然,這樣不科學)

首先交代:stackTrace是由本地方法獲得的,相關的兩個本地方法如下,萬惡的native是吧,筆者認爲是和JVM有關。

    /** 本地方法,用於跟蹤異常的棧幀 */
    native int getStackTraceDepth();      /** 獲得追蹤數組的深度 */
    native StackTraceElement getStackTraceElement(int index);  /** 獲得下標爲index的棧幀信息*/

具體如何使用這兩個本地方法或者stackTrace,這個我們還是可以從源碼中看到的。

getStackTrace():是一個公開的接口,給外界調用。

getOurStackTrace(): 則是真正的執行方法。先是查看stackTrace是否爲UNASSIGNED_STACK(初始狀態),或者是否爲空,並且有追蹤標誌。如果滿足,則可以調用本地方法得到數組深度depth,來分配stackTrace的空間,並將數組成員一個一個寫入stackTrace中。

    /**
     *  獲取當前狀態異常的信息,
     *  獲取棧上面的異常信息,(通過本地方法得到)
     *  遍歷每一個異常信息,
     *  賦值給stackTrace進行保存
     * */
    public StackTraceElement[] getStackTrace() {
        return getOurStackTrace().clone();
    }

    private synchronized StackTraceElement[] getOurStackTrace() {
        if (stackTrace == UNASSIGNED_STACK ||
            (stackTrace == null && backtrace != null) /* Out of protocol state */) {
            int depth = getStackTraceDepth();             /** 通過本地方法得到棧幀數組的深度 depth*/
            stackTrace = new StackTraceElement[depth];    /** 初始化一個長度爲depth的棧幀數組*/
            for (int i=0; i < depth; i++)
                stackTrace[i] = getStackTraceElement(i);  /** 得到本地方法中的棧異常清空,復值給stackTrace[i]*/
        } else if (stackTrace == null) {
            return UNASSIGNED_STACK;
        }
        return stackTrace;                               /** 返回棧幀數組 */
    }

 

我們在遇到異常時,經常需要將異常的棧幀追蹤打印出來,使用printStackTrace()。該方法,可以設置一個輸出流參數,比如System.out。將異常輸出到控制檯上,也可以缺省,在缺省的情況下,默認輸出路徑爲System.err。具體代碼如下:

    /**************************************************
     *  打印異常棧,從方法調用出直到異常拋出處
     *  可選參數:PrintStream s ;PrintWriter s。一個輸出流。
     *  默認輸出到System.err
     **************************************************/
    public void printStackTrace() {
        printStackTrace(System.err);
    }
    public void printStackTrace(PrintStream s) {
        printStackTrace(new WrappedPrintStream(s));
    }
    /** 打印異常信息 */
    private void printStackTrace(PrintStreamOrWriter s) {
        // Guard against malicious overrides of Throwable.equals by
        // using a Set with identity equality semantics.
        Set<Throwable> dejaVu =
            Collections.newSetFromMap(new IdentityHashMap<Throwable, Boolean>());
        dejaVu.add(this);

        synchronized (s.lock()) {
            // Print our stack trace
            s.println(this);
            StackTraceElement[] trace = getOurStackTrace();     /** 打印棧幀情況*/
            for (StackTraceElement traceElement : trace)
                s.println("\tat " + traceElement);

            // Print suppressed exceptions, if any
            for (Throwable se : getSuppressed())               /** 打印被壓抑的異常(as try) */
                se.printEnclosedStackTrace(s, trace, SUPPRESSED_CAPTION, "\t", dejaVu);

            // Print cause, if any
            Throwable ourCause = getCause();                    /** 打印異常鏈*/
            if (ourCause != null)
                ourCause.printEnclosedStackTrace(s, trace, CAUSE_CAPTION, "", dejaVu);
        }
    }

在這裏,有一個getSupressed(),該方法是獲得被壓抑的異常。被壓抑的異常在筆者理解中是在try()catch(){}中捕獲的異常。

與壓抑異常相對應的就是RuntimeException,該異常是運行時由於代碼問題而產生的異常(比如數組越界等等,也稱不受檢查異常,即不需要我們進行檢查、捕獲,程序直接會拋出異常,並終止程序)

異常鏈。則可以理解爲一個異常構成的鏈表。其實就是在處理某個異常A時,導致了另一個異常B的產生。那麼B異常的成員變量cause則爲異常A。

 

此外還有一個較爲常用的方法。 fillInStackTrace()。該方法顧名思義就是將跟蹤棧幀進行清空。底層實現也是本地方法。常用於將異常拋給上級處理,如調用該方法,則會重新設置棧幀跟蹤。但是,描述信息和異常名不變。

    /**
     *  清空本異常之前的棧幀跟蹤,通常用於向上級異常處理進行 拋出異常,之後調用
     *  保留原異常的名稱和描述
     *  從拋出異常處重新設置棧幀跟蹤。
     *
     * */
    public synchronized Throwable fillInStackTrace() {
        if (stackTrace != null ||
            backtrace != null /* Out of protocol state */ ) {
            fillInStackTrace(0);
            stackTrace = UNASSIGNED_STACK;
        }
        return this;
    }
    private native Throwable fillInStackTrace(int dummy);

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章