常用的異常有: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);