阿里二面面試題:請你說一下對受檢異常和非受檢異常的理解?


面試題: 請你說一下對受檢異常和非受檢異常的理解?

面試考察點

考察目的: 異常的設計,在程序開發中時非常重要的。好的異常設計能夠合理清晰的反饋程序的問題,提供排查思路。同時,還能夠很好的處理資源回收問題。所以作為有經驗的程序員,必須要了解異常,以及異常的差異和特性。

考察人群: 工作3年以上,3年左右一般都會參與項目中部分核心代碼的編寫。

背景知識分享

在Java中,所有的異常都繼承自java.lang.Throwable,Throwable有兩個直接子類,Error和Exception,如圖所示。

image-20211101144642899

Throwable 類是 Java 語言中所有錯誤(errors)異常(exceptions)的父類。只有繼承於Throwable類或者其子類的異常才能夠被拋出.

下面分別解釋一下這些異常以及特性。

Error錯誤

Error通常是程序無法處理的錯誤,這些錯誤大多數與代碼編寫者執行的操作無關,並且它們是無法被捕獲的,因為它們在應用程序的控制和處理能力之外,比如。

  1. OutOfMemoryError, 內存溢出,比較常見的錯誤,是值內存空間不足以再提供新對象的分配。
  2. StackOverflowError,棧溢出。

以下是模擬程序中出現Error問題的例子。

  • 編寫一段使用內存存儲的程序

    public class ErrorException {
    
        public static void main(String[] args) {
            List<String> list = new ArrayList<String>();
            /*循環創建對象,消耗堆內存*/
            for (int i= 0;i < 100000;i++){
                list.add(new String("Hello World"));
            }
        }
    }
    
  • 為了演示OOM錯誤,需要調整堆內存空間大小,添加VM option。

    把堆內存空間設置為1兆,這個內存空間非常小,所以很容易出現OOM錯誤。

    -Xmx1m
    
  • 執行上面這段程序,錯誤信息如下.

    錯誤信息中描述的是ArrayList在擴容時,發現堆內存空間不足,所以拋出OOM錯誤。

    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    	at java.util.Arrays.copyOf(Arrays.java:3210)
    	at java.util.Arrays.copyOf(Arrays.java:3181)
    	at java.util.ArrayList.grow(ArrayList.java:267)
    	at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:241)
    	at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:233)
    	at java.util.ArrayList.add(ArrayList.java:464)
    	at org.example.cl02.ErrorException.main(ErrorException.java:12)
    

在JVM中,除了程序計數器外,其他區域:方法區(Method Area)虛擬機棧(VM Stack)本地方法棧(Native Method Stack)堆(Heap) 都是可能發生OutOfMemoryError錯誤。

  • 虛擬機棧:如果線程請求的棧深度大於虛擬機棧所允許的深度,將會出現StackOverflowError異常;如果虛擬機動態擴展無法申請到足夠的內存,將出現OutOfMemoryError
  • 本地方法棧和虛擬機棧一樣。
  • 堆:Java 堆可以處於物理上不連續,邏輯上連續,就像我們的磁盤空間一樣,如果堆中沒有內存完成實例分配,並且堆無法擴展時,將會拋出 OutOfMemoryError。
  • 方法區:方法區無法滿足內存分配需求時,將拋出OutOfMemoryError異常。

出現Error類型的錯誤,

Exception

Exception 位於 java.lang 包下,它是一種頂級接口,繼承於 Throwable 類,Exception 類及其子類都是 Throwable 的組成條件。

Exception是運行時的錯誤,它通常是程序運行時出現的可以預料的異常,基本上都需要Catch,然后再進行相關處理。

從前面的類關系圖中可以看到,Exception有兩類異常的實現。

  1. RuntimeException,又稱為非受檢異常,這類異常不強制使用Catch捕獲,我們可以根據實際場景來判斷是否要Catch。
  2. CheckedException,又稱為受檢異常,這類異常必須顯示地通過Catch捕獲。

受檢異常和非受檢異常

Java規范中,對非受查異常和受查異常的定義是這樣的:

The unchecked exception classes are the run-time exception classes and the error classes.

The checked exception classes are all exception classes other than the unchecked exception classes. That is, the checked exception classes are Throwable and all its subclasses other than RuntimeException and its subclasses and Errorand its subclasses.

也就是說,除了 RuntimeException 和其子類,以及error和其子類,其它的所有異常都是 checkedException

受檢異常的實例

受檢異常,是值需要顯示通過Catch捕獲的異常,在Java中,除了RuntimeException以外的異常,都屬於受檢異常(checkedException).

我們以NoSuchMethodException為例,如圖所示,可以明顯看到,該異常在沒有捕獲的情況下,會顯示提示語法錯誤,有兩個解決辦法

  1. Add exception to method signature,表示把這個異常再往上拋。
  2. Surround with try/catch,表示使用try/catch捕獲。

image-20211101164826685

其他常見的受檢異常:

  1. NoSuchFieldException,表示該類沒有指定名稱拋出來的異常。
  2. IllegalAccessException,不允許訪問某個類的異常。
  3. ClassNotFoundException,類沒有找到拋出異常。
  4. IOException,IO異常。
  5. NumberFormatException,數值類型的格式錯誤

受檢異常,之所以強制讓開發者進行捕獲,是因為調用者接收到該異常時,可以清晰的知道哪個地方出問題了,那么調用者可以根據上下文來決定在異常時做何種處理。

比如IOException,出現該異常時,我們可以在Catch中對流資源進行釋放。

非受檢異常實例

非受檢異常,是指不需要調用者顯示捕獲的異常,RuntimeException以及其派生類都屬於非受檢異常。

同樣的代碼,我們拋出RuntimeException時,並沒有任何語法上的錯誤提示。

public class ErrorException {

    public static void main(String[] args) {
        throw new RuntimeException("Occur Exception");
    }
}

常見的非受檢異常有

  1. ArrayIndexOutOfBoundsException,數組越界異常
  2. NullPointerException,空指針異常
  3. IllegalArgumentException,非法參數異常
  4. NegativeArraySizeException,數組長度為負異常
  5. IllegalStateException,非法狀態異常
  6. ClassCastException,類型轉換異常

總結:受檢的異常和非受檢的異常之間最大的區別在於,受檢的異常是由編譯器強制執行的,用於指示不受程序控制的異常情況(例如,I/O 錯誤),而非受檢的異常在運行時發生,用於指示編程錯誤(例如,空指針)

Java異常的最佳實踐

  1. 當被調用的某個方法服務執行其本身的功能含義是,可以使用受檢異常。

  2. 理想情況下,絕對不應將受檢異常用於編程錯誤,在這種情況下,絕對不能把資源錯誤用於程序流控制。

  3. 盡量不要只捕獲java.lang.Exception這種太過於泛的異常類型,應該要捕獲到具體的錯誤類型。比如InterruptedException,原因有兩個

    1. 在多人協作開發時,別人可以通過這些代碼很清晰的理解我們的程序。並且告訴別人更多的信息。
    2. 我們必須要保證程序不會捕捉到不再我們預期范圍內的異常,比如RuntimeException,我們希望這類異常是要往外拋,而不是在內部被捕獲。
  4. 不要把異常吞掉,因為一旦程序出現問題,沒有異常信息很難定位。

  5. 如果希望調用者能夠從異常中進行合理恢復,需要設置為受檢異常類型,如果調用者無法采用任何措施使得程序無法重異常中恢復,需要把該異常設置為非受檢異常。

擴展:一道經典的面試題

一道非常經典的面試題,NoClassDefFoundError 和 ClassNotFoundException 有什么區別

  1. NoClassDefFoundError,表示這個類在編譯時期存在,但是在運行時不能找到合適的類導致的錯誤。例如在運行時我們想調用某個類的方法或者訪問這個類的靜態成員的時候,發現這個類不可用,此時Java虛擬機就會拋出NoClassDefFoundError錯誤。

    可能出現的錯誤情況如下:

    1. 對應的Class在java的classpath中不可用
    2. 你可能用jar命令運行你的程序,但類並沒有在jar文件的manifest文件中的classpath屬性中定義
    3. 可能程序的啟動腳本覆蓋了原來的classpath環境變量
    4. 因為NoClassDefFoundError是java.lang.LinkageError的一個子類,所以可能由於程序依賴的原生的類庫不可用而導致
    5. 檢查日志文件中是否有java.lang.ExceptionInInitializerError這樣的錯誤,NoClassDefFoundError有可能是由於靜態初始化失敗導致的
    6. 如果你工作在J2EE的環境,有多個不同的類加載器,也可能導致NoClassDefFoundError
  2. ClassNotFoundException,它是程序運行期間的異常,比如當我們嘗試在運行時使用反射加載類時,ClassNotFoundException 就會出現。

    @CallerSensitive
    public static Class<?> forName(String className)
      throws ClassNotFoundException {
      Class<?> caller = Reflection.getCallerClass();
      return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
    }
    

總的來說,ClassNotFoundException 和 NoClassDefFoundError 都是由 CLASSPATH 中缺少類引起的,通常是由於缺少 JAR 文件而引起的,但是如果 JVM 認為應用運行時找不到相應的引用,就會拋出 NoClassDefFoundError 錯誤;當你在代碼中顯示的加載類比如 Class.forName() 調用時卻沒有找到相應的類,就會拋出 java.lang.ClassNotFoundException

問題解答

面試題: 請你說一下對受檢異常和非受檢異常的理解?

回答: 受檢異常和非受檢異常,都是派生自Throwable這個類。他們的區別是

受檢異常: 是指需要調用者顯示通過try-catch捕獲的異常

非受檢異常: 是指不需要調用者顯示捕獲的異常。

之所以要定義受檢異常和非受檢異常,是因為在程序中,存在一些需要用戶在編譯期間就去檢查的問題,比如FileNotFoundException、IOException,這些異常涉及資源處理,調用者需要捕獲,其實它可以提醒開發者,如果被調用的方法出現這類異常時,程序應該做好預判並處理,比如IOExcetion,我們需要對流進行關閉操作。

而非受檢發生在運行期間,是程序運行過程中可能發生的錯誤類型,比如NullpointExcetpion,這些異常我們可以捕獲,也可以不捕獲。但是捕獲這些異常只能打印一些日志,除此之外什么都做不了

總結和思考

關於異常模型的設計,有一篇非常好的文章。

http://joeduffyblog.com/2016/02/07/the-error-model/

大家有空可以去了解一下。

關注[跟着Mic學架構]公眾號,獲取更多精品原創


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM