Java基礎--Exception和Error有什麼區別?

1.異常處理機制

Java 語言在設計之初就提供了相對完善的異常處理機制,這也是 Java 得以大行其道的原因之一,因爲這種機制大大降低了編寫和維護可靠程序的門檻。如今,異常處理機制已經成爲現代編程語言的標配。

2.Exception和Error

    1. Exception 和 Error 都是繼承了 Throwable 類,在 Java 中只有 Throwable 類型的實例纔可以被拋出(throw)或者捕獲(catch),它是異常處理機制的基本組成類型。
    1. Exception 和 Error 體現了 Java 平臺設計者對不同異常情況的分類。Exception 是程序正常運行中,可以預料的意外情況,可能並且應該被捕獲,進行相應處理。
    1. Error 是指在正常情況下,不大可能出現的情況,絕大部分的 Error 都會導致程序(比如 JVM 自身)處於非正常的、不可恢復狀態。既然是非正常情況,所以不便於也不需要捕獲,常見的比如 OutOfMemoryError 之類,都是 Error 的子類。
    1. Exception 又分爲可檢查(checked)異常和不檢查(unchecked)異常,可檢查異常在源代碼裏必須顯式地進行捕獲處理,這是編譯期檢查的一部分。前面我介紹的不可查的 Error,是 Throwable 不是 Exception。
    1. 不檢查異常就是所謂的運行時異常,類似 NullPointerException、ArrayIndexOutOfBoundsException 之類,通常是可以編碼避免的邏輯錯誤,具體根據需要來判斷是否需要捕獲,並不會在編譯期強制要求。
      在這裏插入圖片描述

3. 編程時的異常處理

異常處理代碼比較繁瑣,比如我們需要寫很多千篇一律的捕獲代碼,或者在 finally 裏面做一些資源回收工作。隨着 Java 語言的發展,引入了一些更加便利的特性,比如 try-with-resources 和 multiple catch,具體可以參考下面的代碼段。在編譯時期,會自動生成相應的處理邏輯,比如,自動按照約定俗成 close 那些擴展了 AutoCloseable 或者 Closeable 的對象。


try (BufferedReader br = new BufferedReader();
     BufferedWriter writer = new BufferedWriter()) {// Try-with-resources
// do something
catch ( IOException | XEception e) {// Multiple catch
   // Handle it
} 

4.異常處理坑

第一,儘量不要捕獲類似 Exception 這樣的通用異常,而是應該捕獲特定異常,在這裏是 Thread.sleep() 拋出的 InterruptedException。
這是因爲在日常的開發和合作中,我們讀代碼的機會往往超過寫代碼,軟件工程是門協作的藝術,所以我們有義務讓自己的代碼能夠直觀地體現出儘量多的信息,而泛泛的 Exception 之類,恰恰隱藏了我們的目的。另外,我們也要保證程序不會捕獲到我們不希望捕獲的異常。比如,你可能更希望 RuntimeException 被擴散出來,而不是被捕獲。
進一步講,除非深思熟慮了,否則不要捕獲 Throwable 或者 Error,這樣很難保證我們能夠正確程序處理 OutOfMemoryError。
第二,不要生吞(swallow)異常。這是異常處理中要特別注意的事情,因爲很可能會導致非常難以診斷的詭異情況。
生吞異常,往往是基於假設這段代碼可能不會發生,或者感覺忽略異常是無所謂的,但是千萬不要在產品代碼做這種假設!
如果我們不把異常拋出來,或者也沒有輸出到日誌(Logger)之類,程序可能在後續代碼以不可控的方式結束。沒人能夠輕易判斷究竟是哪裏拋出了異常,以及是什麼原因產生了異常。

5.自定義異常

有的時候,我們會根據需要自定義異常,這個時候除了保證提供足夠的信息,還有兩點需要考慮:

  • 是否需要定義成 Checked Exception,因爲這種類型設計的初衷更是爲了從異常情況恢復,作爲異常設計者,我們往往有充足信息進行分類。
  • 在保證診斷信息足夠的同時,也要考慮避免包含敏感信息,因爲那樣可能導致潛在的安全問題。如果我們看 Java 的標準類庫,你可能注意到類似 java.net.ConnectException,出錯信息是類似“ Connection refused (Connection refused)”,而不包含具體的機器名、IP、端口等,一個重要考量就是信息安全。類似的情況在日誌中也有,比如,用戶數據一般是不可以輸出到日誌裏面的。

業界有一種爭論(甚至可以算是某種程度的共識),Java 語言的 Checked Exception 也許是個設計錯誤,反對者列舉了幾點:

  • Checked Exception 的假設是我們捕獲了異常,然後恢復程序。但是,其實我們大多數情況下,根本就不可能恢復。Checked Exception 的使用,已經大大偏離了最初的設計目的。
  • Checked Exception 不兼容 functional 編程,如果你寫過 Lambda/Stream 代碼,相信深有體會。

6. 異常處理機制對性能的影響

當然,很多人也覺得沒有必要矯枉過正,因爲確實有一些異常,比如和環境相關的 IO、網絡等,其實是存在可恢復性的,而且 Java 已經通過業界的海量實踐,證明了其構建高質量軟件的能力。

我們從性能角度來審視一下 Java 的異常處理機制,這裏有兩個可能會相對昂貴的地方:

  • try-catch 代碼段會產生額外的性能開銷,或者換個角度說,它往往會影響 JVM 對代碼進行優化,所以建議僅捕獲有必要的代碼段,儘量不要一個大的 try 包住整段的代碼;與此同時,利用異常控制代碼流程,也不是一個好主意,遠比我們通常意義上的條件語句(if/else、switch)要低效。
  • Java 每實例化一個 Exception,都會對當時的棧進行快照,這是一個相對比較重的操作。如果發生的非常頻繁,這個開銷可就不能被忽略了。

所以,對於部分追求極致性能的底層類庫,有種方式是嘗試創建不進行棧快照的 Exception。這本身也存在爭議,因爲這樣做的假設在於,我創建異常時知道未來是否需要堆棧。問題是,實際上可能嗎?小範圍或許可能,但是在大規模項目中,這麼做可能不是個理智的選擇。如果需要堆棧,但又沒有收集這些信息,在複雜情況下,尤其是類似微服務這種分佈式系統,這會大大增加診斷的難度。

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