Java異常類的層次結構
Throwable是所有異常類的基類。
Throwable包含了其線程創建時線程執行堆棧的快照,它提供了 printStackTrace() 等接口用于獲取堆棧跟蹤數據等信息。
Throwable 分為兩種:Error和Exception。
1.
Error:Error是系統級別的錯誤,無法通過程序處理;此類錯誤一般表示代碼運行時 JVM 出現問題,例如OutOfMemoryError、StackOverflowError、VirtualMachineError等。
2.
Exception是應用程序級別的異常,可以通過程序處理;通常會分為Checked Exception和Unchecked Exception。
- Checked Exception,也稱為受檢異常,在編譯時就需要進行處理,否則會報編譯錯誤,例如IOException、SQLException等。
- Unchecked Exception,也稱為非受檢異常,在運行時才會拋出,不需要進行強制處理,例如NullPointerException、ArrayIndexOutOfBoundsException等。
受檢異常 vs 非受檢異常
受檢異常
- 本質是應用程序執行過程中可能遇到的異常情形,必須由當前調用方處理;即調用方必須捕獲并處理被調方法簽名上聲明的異常;
- 這類異常,在一定程度上它的發生是可以預計的(例如從配置文件讀取配置項,在程序運行之前就可以預計到文件可能不存在),而一旦發生異常狀況,就必須采取某種方式進行處理。受檢異常在編譯期必須顯式地被處理,否則編譯器將無法通過代碼。
在java.lang.Exception類注釋上,說明了Exception及其子類(排除RuntimeException及其子類),都是checked exceptions受檢異常。
/**
* The class Exception and its subclasses are a form of
* {@code Throwable} that indicates conditions that a reasonable
* application might want to catch.
*
* The class Exception and any subclasses that are not also
* subclasses of {@link RuntimeException} are checked
* exceptions Checked exceptions need to be declared in a
* method or constructor's {@code throws} clause if they can be thrown
* by the execution of the method or constructor and propagate outside
* the method or constructor boundary.
*/
public class Exception extends Throwable {...}
非受檢異常
- 在一定程度上它的發生是不可預計的,只有實際運行到那時才可能發生;比如訪問數組越界,程序運行之前是無法預計的。JDK將這類異常命名為RuntimeException運行時異常也恰如其分。
- 這類異常的發生通常是由于程序 bug 所致,應該盡量通過預先檢查進行規避;比如在面對可能拋NullPointerException的地方時主動判斷是不是null 并處理、循環處理時要檢查下標邊界防止IndexOutOfBounds。
在java.lang.RuntimeException類注釋上,說明了RuntimeException及其子類,都是Unchecked Exception非受檢異常。
/**
* RuntimeException is the superclass of those
* exceptions that can be thrown during the normal operation of the
* Java Virtual Machine.
*
* RuntimeException and its subclasses are unchecked exceptions.
* Unchecked exceptions do not need to be
* declared in a method or constructor's {@code throws} clause if they
* can be thrown by the execution of the method or constructor and
* propagate outside the method or constructor boundary.
*
* @author Frank Yellin
* @jls 11.2 Compile-Time Checking of Exceptions
* @since JDK1.0
*/
public class RuntimeException extends Exception {...}
Java異常處理機制
Java 的異常處理,主要是通過 5 個關鍵字來實現:try、catch、throw、throws 和 finally。
通過捕獲和處理異常,能夠在程序出現異常情況時提供更好的容錯機制,保證程序的穩定性和可靠性。
throws與throw
- throws出現在方法簽名的聲明中,表示該方法可能會拋出的異常,然后交給上層調用它的方法程序處理,允許throws后面跟著多個異常類型;一般用在程序運行之前就預計可能發生的異常場景上,讓調用方顯示處理。
- throw只會出現在方法體中,當方法在執行過程中遇到異常情況時,將異常信息封裝為異常對象,然后throw出去。
throw 關鍵字的作用是拋出一個異常,但是它的作用不僅僅局限于拋出異常。在一些情況下,throw 關鍵字還可以實現異常類型的轉換。這是因為 Java 中的異常繼承關系,子類異常對象可以轉換為父類異常對象,而 throw 關鍵字可以將子類異常對象強制轉換為父類異常對象。
public void doSomething() throws Exception {
try {
// do something
} catch (ChildException e) { // 子類異常
throw (Exception)e; // 異常類型轉換為父類異常
}
}
throws表示出現異常的一種可能性,并不一定會發生這些異常;throw則是拋出了異常,執行throw則一定拋出了某種異常對象。
關于finally特殊問題和Java異常處理的最佳實踐,后面用單獨的文章討論。
覆蓋父類方法時的異常聲明
從父類繼承,并且子類重寫/覆蓋的方法簽名,如何聲明異常?
子類重寫父類方法的時候,如何確定異常拋出聲明的類型。主要有4個原則:
1.
子類重寫父類方法時,要拋出與父類一致的異常,或者不拋出異常
2.
子類重寫父類方法時,子類拋出的異常不能超過父類的受檢異常類型范圍
即如果父類的方法聲明了受檢異常T,則子類在重寫該方法的時候聲明的異常不能是 T的父類;只能是T及T的子類。
3.
子類重寫的方法可以拋出任意非受檢異常
4.
子類在重寫父類的具有異常聲明的方法的同時,又去實現了具有相同方法名稱的接口且該接口中的方法也具有異常聲明,則子類中的重寫的方法,要么不拋出異常,要么拋出父類中方法聲明異常與接口中方法聲明的異常的交集。
自定義異常
在 Java 中,通常情況下已有的內置異常類能夠滿足應用的基礎使用需求。但有些情況下,需要自定義異常來滿足特別的需求。
最常見的就是用精確的命名描述異常
- 例如,某個特定case的異常,讓它見名知意。當程序需要對異常情況進行更加具體和準確的描述時,可以自定義異常類。
如何自定義異常就不多描述了,這里著重說一點關于自定義異常時需要注意的地方。
重寫fillInStackTrace方法
Java 異常在程序運行時發生,會導致程序的執行流程被中斷并進行一系列的異常處理操作,這會對程序的性能產生一定的影響。其中性能開銷最大的是填充堆棧。
默認情況下,是由Throwable的fillInStackTrace方法完成,它是一個native方法;作用是獲取當前線程的堆棧信息,填充到跟蹤元素中。
public synchronized Throwable fillInStackTrace() {
if (stackTrace != null || backtrace != null ) {// 如果沒填充過……
fillInStackTrace(0); // 獲取當前線程的堆棧跟蹤信息
stackTrace = UNASSIGNED_STACK; // 填充堆棧跟蹤信息
}
return this;// 如果已經填充過,直接返回當前異常對象
}
一般來說自定義的業務異常如果確實不需要stacktrace的話,可以覆寫該方法,返回this,提高性能。
@Override
public synchronized Throwable fillInStackTrace() {
// return super.fillInStackTrace();
return this;
}
當前還有其他實現方式:
- jvm參數控制棧深度,物理屏蔽;
- logback日志框架控制堆棧的輸出深度,邏輯屏蔽;
- jvm本身對某些異常也做了優化,jvm有個參數OmitStackTraceInFastThrow(省略異??焖賿伋觯?, 如果檢測到在代碼里某個位置連續多次拋出同一類型異常的話,C2即時編譯器會決定用Fast Throw方式來拋出異常,而異常Trace即詳細的異常棧信息會被清空。