Java異常常見面試題

1. Error 和 Exception 區別是什麼?

Error 類型的錯誤通常爲虛擬機相關錯誤,如系統崩潰,內存不足,堆棧溢出等,編譯器不會對這類錯誤進行檢測,JAVA 應用程序也不應對這類錯誤進行捕獲,一旦這類錯誤發生,通常應用程序會被終止,僅靠應用程序本身無法恢復;
Exception 類的錯誤是可以在應用程序中進行捕獲並處理的,通常遇到這種錯誤,應對其進行處理,使應用程序可以繼續正常運行。

2. 運行時異常和一般異常(受檢異常)區別是什麼?

運行時異常包括 RuntimeException 類及其子類,表示 JVM 在運行期間可能出現的異常。Java 編譯器不會檢查運行時異常。
受檢異常是Exception 中除 RuntimeException 及其子類之外的異常。Java 編譯器會檢查受檢異常。
RuntimeException異常和受檢異常之間的區別:是否強制要求調用者必須處理此異常,如果強制要求調用者必須進行處理,那麼就使用受檢異常,否則就選擇非受檢異常(RuntimeException)。一般來講,如果沒有特殊的要求,我們建議使用RuntimeException異常。

3. JVM 是如何處理異常的?

在一個方法中如果發生異常,這個方法會創建一個異常對象,並轉交給 JVM,該異常對象包含異常名稱,異常描述以及異常發生時應用程序的狀態。創建異常對象並轉交給 JVM 的過程稱爲拋出異常。可能有一系列的方法調用,最終才進入拋出異常的方法,這一系列方法調用的有序列表叫做調用棧。
JVM 會順着調用棧去查找看是否有可以處理異常的代碼,如果有,則調用異常處理代碼。當 JVM 發現可以處理異常的代碼時,會把發生的異常傳遞給它。如果 JVM 沒有找到可以處理該異常的代碼塊,JVM 就會將該異常轉交給默認的異常處理器(默認處理器爲 JVM 的一部分),默認異常處理器打印出異常信息並終止應用程序。

4. throw 和 throws 的區別是什麼?

Java 中的異常處理除了包括捕獲異常和處理異常之外,還包括聲明異常和拋出異常,可以通過 throws 關鍵字在方法上聲明該方法要拋出的異常,或者在方法內部通過 throw 拋出異常對象。
throws 關鍵字和 throw 關鍵字在使用上的幾點區別如下

  • throw 關鍵字用在方法內部,只能用於拋出一種異常,用來拋出方法或代碼塊中的異常,受查異常和非受查異常都可以被拋出。
  • throws 關鍵字用在方法聲明上,可以拋出多個異常,用來標識該方法可能拋出的異常列表。一個方法用 throws 標識了可能拋出的異常列表,調用該方法的方法中必須包含可處理異常的代碼,否則也要在方法簽名中用 throws 關鍵字聲明相應的異常。

5. final、finally、finalize 有什麼區別?

  • final可以修飾類、變量、方法,修飾類表示該類不能被繼承、修飾方法表示該方法不能被重寫、修飾變量表示該變量是一個常量不能被重新賦值。
  • finally一般作用在try-catch代碼塊中,在處理異常的時候,通常我們將一定要執行的代碼方法finally代碼塊中,表示不管是否出現異常,該代碼塊都會執行,一般用來存放一些關閉資源的代碼。
  • finalize是一個方法,屬於Object類的一個方法,而Object類是所有類的父類,Java 中允許使用 finalize()方法在垃圾收集器將對象從內存中清除出去之前做必要的清理工作。

6. NoClassDefFoundError 和 ClassNotFoundException 區別?

NoClassDefFoundError 是一個 Error 類型的異常,是由 JVM 引起的,不應該嘗試捕獲這個異常。
引起該異常的原因是 JVM 或 ClassLoader 嘗試加載某類時在內存中找不到該類的定義,該動作發生在運行期間,即編譯時該類存在,但是在運行時卻找不到了,可能是變異後被刪除了等原因導致;
ClassNotFoundException 是一個受查異常,需要顯式地使用 try-catch 對其進行捕獲和處理,或在方法簽名中用 throws 關鍵字進行聲明。當使用 Class.forName, ClassLoader.loadClass 或 ClassLoader.findSystemClass 動態加載類到內存的時候,通過傳入的類路徑參數沒有找到該類,就會拋出該異常;另一種拋出該異常的可能原因是某個類已經由一個類加載器加載至內存中,另一個加載器又嘗試去加載它。

7. try-catch-finally 中哪個部分可以省略?

答:catch 可以省略
原因
更爲嚴格的說法其實是:try只適合處理運行時異常,try+catch適合處理運行時異常+普通異常。也就是說,如果你只用try去處理普通異常卻不加以catch處理,編譯是通不過的,因爲編譯器硬性規定,普通異常如果選擇捕獲,則必須用catch顯示聲明以便進一步處理。而運行時異常在編譯時沒有如此規定,所以catch可以省略,你加上catch編譯器也覺得無可厚非。
理論上,編譯器看任何代碼都不順眼,都覺得可能有潛在的問題,所以你即使對所有代碼加上try,代碼在運行期時也只不過是在正常運行的基礎上加一層皮。但是你一旦對一段代碼加上try,就等於顯示地承諾編譯器,對這段代碼可能拋出的異常進行捕獲而非向上拋出處理。如果是普通異常,編譯器要求必須用catch捕獲以便進一步處理;如果運行時異常,捕獲然後丟棄並且+finally掃尾處理,或者加上catch捕獲以便進一步處理。
至於加上finally,則是在不管有沒捕獲異常,都要進行的“掃尾”處理。

8. try-catch-finally 中,如果 catch 中 return 了,finally 還會執行嗎?

答:會執行,在 return 前執行。
注意:在 finally 中改變返回值的做法是不好的,因爲如果存在 finally 代碼塊,try中的 return 語句不會立馬返回調用者,而是記錄下返回值待 finally 代碼塊執行完畢之後再向調用者返回其值,然後如果在 finally 中修改了返回值,就會返回修改後的值。顯然,在 finally 中返回或者修改返回值會對程序造成很大的困擾,C#中直接用編譯錯誤的方式來阻止程序員幹這種齷齪的事情,Java 中也可以通過提升編譯器的語法檢查級別來產生警告或錯誤。

代碼示例1:

public static int getInt() {
    int a = 10;
    try {
        System.out.println(a / 0);
        a = 20;
    } catch (ArithmeticException e) {
        a = 30;
        return a;
        /*
         * return a 在程序執行到這一步的時候,這裏不是return a 而是 return 30;這個返回路徑就形成了
         * 但是呢,它發現後面還有finally,所以繼續執行finally的內容,a=40
         * 再次回到以前的路徑,繼續走return 30,形成返回路徑之後,這裏的a就不是a變量了,而是常量30
         */
    } finally {
        a = 40;
    }
 return a;
}

  

執行結果:30
代碼示例2:

public static int getInt() {
    int a = 10;
    try {
        System.out.println(a / 0);
        a = 20;
    } catch (ArithmeticException e) {
        a = 30;
        return a;
    } finally {
        a = 40;
        //如果這樣,就又重新形成了一條返回路徑,由於只能通過1個return返回,所以這裏直接返回40
        return a; 
    }
}

  

執行結果:40

9. 類 ExampleA 繼承 Exception,類 ExampleB 繼承ExampleA。

有如下代碼片斷:

try {
 throw new ExampleB("b")
} catch(ExampleA e){
 System.out.println("ExampleA");
} catch(Exception e){
 System.out.println("Exception");
}

  

請問執行此段代碼的輸出是什麼?

輸出:ExampleA。(根據里氏代換原則[能使用父類型的地方一定能使用子類型],抓取 ExampleA 類型異常的 catch 塊能夠抓住 try 塊中拋出的 ExampleB 類型的異常)
面試題 - 說出下面代碼的運行結果。(此題的出處是《Java 編程思想》一書)

class Annoyance extends Exception {
}
class Sneeze extends Annoyance {
}
class Human {
 public static void main(String[] args)
 throws Exception {
  try {
   try {
    throw new Sneeze();
   } catch ( Annoyance a ) {
    System.out.println("Caught Annoyance");
    throw a;
   }
  } catch ( Sneeze s ) {
   System.out.println("Caught Sneeze");
   return ;
  } finally {
   System.out.println("Hello World!");
  }
 }
}

  

結果

Caught Annoyance
Caught Sneeze
Hello World!

 

10. 常見的 RuntimeException 有哪些?

    • ClassCastException(類轉換異常)
    • IndexOutOfBoundsException(數組越界)
    • NullPointerException(空指針)
    • ArrayStoreException(數據存儲異常,操作數組時類型不一致)
    • 還有IO操作的BufferOverflowException異常

11. Java常見異常有哪些

java.lang.IllegalAccessError:違法訪問錯誤。當一個應用試圖訪問、修改某個類的域(Field)或者調用其方法,但是又違反域或方法的可見性聲明,則拋出該異常。
java.lang.InstantiationError:實例化錯誤。當一個應用試圖通過Java的new操作符構造一個抽象類或者接口時拋出該異常.
java.lang.OutOfMemoryError:內存不足錯誤。當可用內存不足以讓Java虛擬機分配給一個對象時拋出該錯誤。
java.lang.StackOverflowError:堆棧溢出錯誤。當一個應用遞歸調用的層次太深而導致堆棧溢出或者陷入死循環時拋出該錯誤。
java.lang.ClassCastException:類造型異常。假設有類A和B(A不是B的父類或子類),O是A的實例,那麼當強制將O構造爲類B的實例時拋出該異常。該異常經常被稱爲強制類型轉換異常。
java.lang.ClassNotFoundException:找不到類異常。當應用試圖根據字符串形式的類名構造類,而在遍歷CLASSPAH之後找不到對應名稱的class文件時,拋出該異常。
java.lang.ArithmeticException:算術條件異常。譬如:整數除零等。
java.lang.ArrayIndexOutOfBoundsException:數組索引越界異常。當對數組的索引值爲負數或大於等於數組大小時拋出。
java.lang.IndexOutOfBoundsException:索引越界異常。當訪問某個序列的索引值小於0或大於等於序列大小時,拋出該異常。
java.lang.InstantiationException:實例化異常。當試圖通過newInstance()方法創建某個類的實例,而該類是一個抽象類或接口時,拋出該異常。
java.lang.NoSuchFieldException:屬性不存在異常。當訪問某個類的不存在的屬性時拋出該異常。
java.lang.NoSuchMethodException:方法不存在異常。當訪問某個類的不存在的方法時拋出該異常。
java.lang.NullPointerException:空指針異常。當應用試圖在要求使用對象的地方使用了null時,拋出該異常。譬如:調用null對象的實例方法、訪問null對象的屬性、計算null對象的長度、使用throw語句拋出null等等。
java.lang.NumberFormatException:數字格式異常。當試圖將一個String轉換爲指定的數字類型,而該字符串確不滿足數字類型要求的格式時,拋出該異常。
java.lang.StringIndexOutOfBoundsException:字符串索引越界異常。當使用索引值訪問某個字符串中的字符,而該索引值小於0或大於等於序列大小時,拋出該異常。

異常處理-阿里巴巴Java開發手冊

  1. 【強制】Java 類庫中定義的可以通過預檢查方式規避的RuntimeException異常不應該通過catch 的方式來處理,比如:NullPointerException,IndexOutOfBoundsException等等。說明:無法通過預檢查的異常除外,比如,在解析字符串形式的數字時,可能存在數字格式錯誤,不得不通過catch NumberFormatException來實現。正例:if (obj != null) {…} 反例:try { obj.method(); } catch (NullPointerException e) {…}

  2. 【強制】異常不要用來做流程控制,條件控制。說明:異常設計的初衷是解決程序運行中的各種意外情況,且異常的處理效率比條件判斷方式要低很多。

  3. 【強制】catch時請分清穩定代碼和非穩定代碼,穩定代碼指的是無論如何不會出錯的代碼。對於非穩定代碼的catch儘可能進行區分異常類型,再做對應的異常處理。說明:對大段代碼進行try-catch,使程序無法根據不同的異常做出正確的應激反應,也不利於定位問題,這是一種不負責任的表現。正例:用戶註冊的場景中,如果用戶輸入非法字符,或用戶名稱已存在,或用戶輸入密碼過於簡單,在程序上作出分門別類的判斷,並提示給用戶。

  4. 【強制】捕獲異常是爲了處理它,不要捕獲了卻什麼都不處理而拋棄之,如果不想處理它,請將該異常拋給它的調用者。最外層的業務使用者,必須處理異常,將其轉化爲用戶可以理解的內容。

  5. 【強制】有try塊放到了事務代碼中,catch異常後,如果需要回滾事務,一定要注意手動回滾事務。

  6. 【強制】finally塊必須對資源對象、流對象進行關閉,有異常也要做try-catch。說明:如果JDK7及以上,可以使用try-with-resources方式。

  7. 【強制】不要在finally塊中使用return。說明:try塊中的return語句執行成功後,並不馬上返回,而是繼續執行finally塊中的語句,如果此處存在return語句,則在此直接返回,無情丟棄掉try塊中的返回點。反例:

private int x = 0;
public int checkReturn() {
    try {
        // x等於1,此處不返回
        return ++x;
    } finally {
        // 返回的結果是2
        return ++x;
    }
}

 

    1. 【強制】捕獲異常與拋異常,必須是完全匹配,或者捕獲異常是拋異常的父類。說明:如果預期對方拋的是繡球,實際接到的是鉛球,就會產生意外情況。

    2. 【強制】在調用RPC、二方包、或動態生成類的相關方法時,捕捉異常必須使用Throwable類來進行攔截。說明:通過反射機制來調用方法,如果找不到方法,拋出NoSuchMethodException。什麼情況會拋出NoSuchMethodError呢?二方包在類衝突時,仲裁機制可能導致引入非預期的版本使類的方法簽名不匹配,或者在字節碼修改框架(比如:ASM)動態創建或修改類時,修改了相應的方法簽名。這些情況,即使代碼編譯期是正確的,但在代碼運行期時,會拋出NoSuchMethodError。

    3. 【推薦】方法的返回值可以爲null,不強制返回空集合,或者空對象等,必須添加註釋充分說明什麼情況下會返回null值。說明:本手冊明確防止NPE是調用者的責任。即使被調用方法返回空集合或者空對象,對調用者來說,也並非高枕無憂,必須考慮到遠程調用失敗、序列化失敗、運行時異常等場景返回null的情況。

    4. 【推薦】防止NPE,是程序員的基本修養,注意NPE產生的場景:1) 返回類型爲基本數據類型,return包裝數據類型的對象時,自動拆箱有可能產生NPE。反例:public int f() { return Integer對象}, 如果爲null,自動解箱拋NPE。2) 數據庫的查詢結果可能爲null。3) 集合裏的元素即使isNotEmpty,取出的數據元素也可能爲null。4) 遠程調用返回對象時,一律要求進行空指針判斷,防止NPE。5) 對於Session中獲取的數據,建議進行NPE檢查,避免空指針。6) 級聯調用obj.getA().getB().getC();一連串調用,易產生NPE。
      正例:使用JDK8的Optional類來防止NPE問題。

    5. 【推薦】定義時區分unchecked / checked 異常,避免直接拋出new RuntimeException(),更不允許拋出Exception或者Throwable,應使用有業務含義的自定義異常。推薦業界已定義過的自定義異常,如:DAOException / ServiceException等。

    6. 【參考】對於公司外的http/api開放接口必須使用“錯誤碼”;而應用內部推薦異常拋出;跨應用間RPC調用優先考慮使用Result方式,封裝isSuccess()方法、“錯誤碼”、“錯誤簡短信息”。說明:關於RPC方法返回方式使用Result方式的理由:1)使用拋異常返回方式,調用方如果沒有捕獲到就會產生運行時錯誤。2)如果不加棧信息,只是new自定義異常,加入自己的理解的error message,對於調用端解決問題的幫助不會太多。如果加了棧信息,在頻繁調用出錯的情況下,數據序列化和傳輸的性能損耗也是問題。

轉載參考:https://mp.weixin.qq.com/s/QCzZOOQ7QrZFkNtsIQok9w

 

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