Java核心技術----Exception和Error的區別

1.概述

(1) Exception 和 Error 都是繼承了 Throwable 類,在 Java 中只有 Throwable 類型的實例纔可以被拋出(throw)或者捕獲(catch),它是異常處理機制的基本組成類型。Exception 和 Error 體現了 Java 平臺設計者對不同異常情況的分類。

(2) Exception 是程序正常運行中,可以預料的意外情況,可能並且應該被捕獲,進行相應處理。

(3) Error 是指在正常情況下,不大可能出現的情況,絕大部分的 Error 都會導致程序(比如 JVM 自身)處於非正常的、不可恢復狀態。既然是非正常情況,所以不便於也不需要捕獲,常見的比如 OutOfMemoryError 之類,都是 Error 的子類。

(4) Exception 又分爲受查(checked)異常和非受查(unchecked)異常:

  • 受查異常在源代碼裏必須顯式地進行捕獲處理,這是編譯期檢查的一部分。如果不進行捕獲或者拋出聲明處理,編譯都不會通過。
ClassNotFoundException, NamingException, ServletException, 
SQLException, IOException
  • 非受查異常就是Error類和運行時異常(RuntimeException)及其子類,通常是可以編碼避免的邏輯錯誤,具體根據需要來判斷是否需要捕獲,並不會在編譯期強制要求。
OutOfMemoryError, UndeclaredThrowableException, IllegalArgumentException, 
IllegalMonitorStateException, NullPointerException, IllegalStateException, 
IndexOutOfBoundsException

一個函數儘管拋出了多個異常,但是隻有一個異常可被傳播到調用端。最後被拋出的異常時唯一被調用端接收的異常,其他異常都會被吞沒掩蓋。如果調用端要知道造成失敗的最初原因,程序之中就絕不能掩蓋任何異常。

【不要推諉或延遲處理異常,就地解決最好,並且需要實實在在的進行處理,而不是隻捕捉,不動作】


2.具體分析

相關類圖
avatar

(1)捕獲機制:try-catch-finally

avatar

  • try{}語句塊:裏面是要檢測的Java代碼,可能會拋出異常,也可能會正常運行

  • catch(異常類型){}塊:是當Java運行時,系統接收到try塊中所拋出異常對象時,會尋找能處理這一異常catch塊來進行處理,注意異常類型需要與try{}語句塊可能拋出的異常要匹配。可以有多個catch塊。不同的異常類型對應不同的處理代碼。

  • finally{}語句塊:不管系統有沒有拋出異常,都會去執行,一般用來釋放資源。除了在之前執行了System.exit(0)。
    avatar

隨着 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
} 
注意點:

1. 不要在finally代碼塊中處理返回值。
2. 當遇到return語句的時候,執行函數會立刻返回。但是,在Java語言中,如果存在finally就會有例外。除了return語句,try代碼塊中的break或continue語句也可能使控制權進入finally代碼塊
3. 請勿在try代碼塊中調用return、break或continue語句。萬一無法避免,一定要確保finally的存在不會改變函數的返回值。
4.對於對象引用,要特別小心,如果在finally代碼塊中對函數返回的對象成員屬性進行了修改,即使不在finally塊中顯式調用return語句,這個修改也會作用於返回值上。
5.勿將異常用於控制流

(2)拋出機制:throw/throws

   avatar     avatar

  • throw:手動拋出異常,一般由程序員在方法內拋出Exception的子類異常

  • throws:聲明在方法名之後,告訴調用者,該方法可能會拋出異常,也就是說異常發生後會拋給調用者,由調用者處理異常。

注意點:

當throw的是受查異常時,必須在方法名後加throws

(3)異常處理的原則

1. 儘量不要捕獲類似 Exception 這樣的通用異常,而是應該捕獲特定異常
   讓自己的代碼能夠直觀地體現出儘量多的信息,而泛泛的 Exception 之類,恰恰隱藏了我們的目的。另外,我們也要保證程序不會捕獲到我們不希望捕獲的異常。

2. 不要生吞(swallow)異常
   如果我們不把異常拋出來,或者也沒有輸出到日誌(Logger)之類,程序可能在後續代碼以不可控的方式結束。沒人能夠輕易判斷究竟是哪裏拋出了異常,以及是什麼原因產生了異常。

3. Throw early, catch late 原則

   throw early:

public void readPreferences(String filename) {
    Objects. requireNonNull(filename); //jdk1.7
    //...perform other operations... 
    InputStream in = new FileInputStream(filename);
     //...read the preferences file...
}

   至於“catch late”,其實是我們經常苦惱的問題,捕獲異常後,需要怎麼處理呢?最差的處理方式,就是我前面提到的“生吞異常”,本質上其實是掩蓋問題。如果實在不知道如何處理,可以選擇保留原有異常的 cause 信息,直接再拋出或者構建新的異常拋出去。在更高層面,因爲有了清晰的(業務)邏輯,往往會更清楚合適的處理方式是什麼。

(4)性能角度來審視Java 的異常處理機制

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

  • try-catch 代碼段會產生額外的性能開銷,或者換個角度說,它往往會影響 JVM 對代碼進行優化,所以建議僅捕獲有必要的代碼段,儘量不要一個大的 try 包住整段的代碼;與此同時,利用異常控制代碼流程,也不是一個好主意,遠比我們通常意義上的條件語句(if/else、switch)要低效。

  • Java 每實例化一個 Exception,都會對當時的棧進行快照,這是一個相對比較重的操作。如果發生的非常頻繁,這個開銷可就不能被忽略了。

【當我們的服務出現反應變慢、吞吐量下降的時候,檢查發生最頻繁的 Exception 也是一種思路。】


3.擴展

NoClassDefFoundError 和 ClassNotFoundException 有什麼區別 ?

  • java.lang.NoClassDefFoundError:

    • 說明: 類加載器試圖加載類的定義,但是找不到這個類的定義,而實際上這個類文件是存在的。是一種 unchecked exception(也稱 runtime exception)

    • 原因: 一般需要檢查這個類定義中的初始化部分(如類屬性定義、static 塊等)的代碼是否有拋異常的可能,如果是 static 塊,可以考慮在其中將異常捕獲並打印堆棧等,或者直接在對類進行初始化調用(如 new Foobar())時作 try catch。

  • java.lang.ClassNotFoundException:

    • 說明: 從規範說明看, java.lang.ClassNotFoundException 異常拋出的根本原因是類文件找不到。是一種 checked exception。

    • 原因: 這個異常產生的原因是缺少了 .class 文件,比如少引了某個 jar,解決方法通常需要檢查一下 classpath 下能不能找到包含缺失 .class 文件的 jar。

現在非常火熱的反應式編程(Reactive Stream),因爲其本身是異步、基於事件機制的,所以出現異常情況,決不能簡單拋出去;另外,由於代碼堆棧不再是同步調用那種垂直的結構,這裏的異常處理和日誌需要更加小心,我們看到的往往是特定 executor 的堆棧,而不是業務方法調用關係。對於這種情況,你有什麼好的辦法嗎?

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