重學Java異常體系

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即詳細的異常棧信息會被清空。
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內容