來了!令人頭疼的 Java 異常面試總結(jié)
異常簡介
異常類層次結(jié)構(gòu)

從結(jié)構(gòu)圖可以看出,所有異常均繼承自 Throwable 類,它有兩個重要的子類:Exception 和 Error ,各自又包含大量子類。
Exception
程序本身可以處理的異常,又可以分為 受檢異常 和 非受檢異常 ,受檢異常 可以用 try...catch... 語句進行捕獲處理,而且能從異常中恢復(fù)。但 非受檢異常 是程序運行時錯誤,會導(dǎo)致程序崩潰而無法恢復(fù)。
受檢異常
編譯器要求必須處理的異常,正確的程序在運行時,經(jīng)常會出現(xiàn)、預(yù)期范圍內(nèi)的情況。一旦發(fā)生該類異常,就必須使用某種方式進行處理。包括除開 RuntimeException 及其子類之外的 Exception 異常。編譯器會檢查此類異常,所以我們必須使用 throws 進行拋出或者 try...catch 進行捕獲,否則將導(dǎo)致編譯失敗。
非受檢異常
編譯器不會檢查而且也不要求我們進行處理,即就算在程序中出現(xiàn)了此類異常,即便我們沒有用 try...catch 進行捕獲或者用 throws 進行拋出,編譯都會成功。包括 RuntimeException 及其子類和錯誤 Error.
同時也可以分為:運行時異常和編譯時異常。
運行時異常
RuntimeException 類及其子類,表示 JVM 在運行期間可能出現(xiàn)的異常,Java 編譯器不會檢查它。沒有通過 throws 拋出或 try...catch 捕獲,仍然可以編譯通過,常見的有 NullPointerException、ArrayIndexOutBoundException、ClassCastException、ArithmeticException、NumberFormatException、IllegalArgumentException;
編譯時異常
Exception 中除開運行時異常之外的異常,Java 編譯器會檢查它,一旦出現(xiàn),必須使用 throws 進行聲明拋出,或者使用 try...catch 進行捕獲異常,否則不能通過編譯。常見的有 ClassNotFoundException、IOException。在程序中,通常不會自定義該類異常,而是直接用系統(tǒng)提供的異常類,該異常必須手動在代碼中添加捕獲語句來處理。
Error
程序無法處理的錯誤,表示程序運行過程中教嚴重的問題,大多與 coder 所做操作無關(guān),而是代碼運行時 JVM 出現(xiàn)的問題。此時說明故障發(fā)生于虛擬機本身、或者發(fā)生在虛擬機試圖執(zhí)行應(yīng)用時。
Throwable 常用方法
| 方法 | 說明 |
|---|---|
public String getMessage() | 返回異常發(fā)生時的簡要描述 |
public String toString() | 返回異常發(fā)生時的詳細信息 |
public String getLocalizeMessage() | 返回異常對象的本地化信息,若子類重寫該方法,可以生成本地化信息,若未重寫,則返回信息同 getMessage() 方法 |
public void printStackTrace() | 在控制臺中打印異常對象封裝的異常信息 |
try-catch-finally 和 try-with-resources
try-catch-finally try :用于捕獲異常,后接零個或多個 catch,沒有catch則必須加上finally;catch:用于處理 try捕獲到的異常;finally:無論是否捕獲/處理異常, finally塊中內(nèi)容均會執(zhí)行,就算try或catch中有return語句,finally中代碼也將在方法返回之前執(zhí)行;try-with-resources
當(dāng)我們有必須要關(guān)閉的資源時,建議優(yōu)先使用 try-with-resources,這樣寫出的代碼更加簡短清晰。
兩者對比
// try-catch-finally
Scanner scanner = null;
try {
scanner = new Scanner(new File("D:/demo.txt"));
while (scanner.hasNext()) {
System.out.println(scanner.nextLine());
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (scanner != null) {
scanner.close();
}
}
// try-with-resources
try (Scanner scanner = new Scanner(new File("D:/demo.txt"))) {
while (scanner.hasNext()) {
System.out.println(scanner.nextLine());
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
異常處理
Java 中,異常處理機制分為 聲明異常、拋出異常和捕獲異常,根據(jù)異常的情況,可以對異常進行不同處理:

聲明異常
對于知道如何進行處理的異常,一般要進行捕獲,但此時不知道如何將處理的異常繼續(xù)傳遞下去,可以通過在方法簽名中使用 throws 來聲明可能拋出的異常,有如下兩點需要注意:
非受檢異常(Error、RuntimeException 及其子類) 不能使用 throws關(guān)鍵字來聲明要拋出的異常;一個方法出現(xiàn)編譯時異常,就需要 try...catch/throws進行處理,否則會導(dǎo)致編譯失敗 ;
拋出異常
一旦覺得某些異常無法處理,但同時又不用我們進行處理,那我們就可以將其拋出。一般是使用 throw 在方法內(nèi)部拋出一個 Throwable 類型的異常。
捕獲異常
程序在運行前一般不會報錯,但是運行后可能出現(xiàn)某些未知錯誤,如果不想直接拋出給上一級處理,那我們就需要通過 try...catch... 的形式對異常進行捕獲,然后根據(jù)不同的情況來進行相應(yīng)處理。
異常常見面試題
Error 和 Exception 的區(qū)別?
Exception 類的異常能夠在程序中進行捕獲并處理,遇到該類異常,應(yīng)該進行處理,從而使程序能夠繼續(xù)正常運行;
Error 類的錯誤一般是虛擬機相關(guān)錯誤,如系統(tǒng)崩潰、內(nèi)存不足、堆棧溢出等,編譯器不會檢測這類錯誤。我們也不會對這類錯誤進行捕獲,一旦發(fā)生,一般都會導(dǎo)致程序崩潰無法恢復(fù);
運行時異常和受檢異常的區(qū)別?
運行時異常包括 RuntimeException 及其子類,表示 JVM 運行期間可能出現(xiàn)的異常,不會被 Java 編譯器檢查。
而受檢異常是除開 RuntimeException 及其子類之外的其他 Exception,會被 Java 編譯器檢查。
兩者的 區(qū)別 在于:是否需要調(diào)用者必須處理該異常,如果必須處理,則一般使用受檢異常,否則一般選擇非受檢異常(RuntimeException);
throw 和 throws 的區(qū)別?
throw:用于在方法內(nèi)部拋出異常對象 throw用在方法體內(nèi),表示拋出異常,由方法體內(nèi)的語句處理;throw是具體向外拋出異常的動作,所以拋出的是一個異常實例,執(zhí)行throw一定是拋出了某種異常;throws:用于在方法簽名上聲明該方法所要拋出的異常 throws語句使用在方法聲明后,表示若拋出異常,則由該方法的調(diào)用者來進行異常的處理;throws主要是聲明這個方法會拋出某種類型的異常,讓它的使用者要知道需要捕獲的異常的類型;throws表示出現(xiàn)異常的一種可能性,并非一定發(fā)生該種異常;
final、finally、finallize 的區(qū)別?
final 用于修飾類、方法、變量,修飾類時表示類不能被繼承;修飾方法時表示方法不能別重寫,但是能夠被重載;修飾變量時表示該變量是一個常量無法被重寫賦值;
finally 一般作用于 try...catch 代碼塊,處理異常時,通常將必須要執(zhí)行的代碼放在 finally 代碼塊中,表示無論是否出現(xiàn)異常,此代碼塊均執(zhí)行,一般用來存放一些關(guān)閉資源的代碼;
finallize 是一個方法,屬于 Object 類,Java 允許用 finallize() 方法在垃圾回收器將對象從內(nèi)存中清除前做一些必要的清理工作;
常見的 RuntimeException 異常?
ClassCastExceptionIndexOutOfBoundsExceptionNullPointerExceptionArrayStoreExceptionBufferOverFlowException
JVM 如何處理異常?
一旦某方法發(fā)生異常,該方法就會創(chuàng)建一個異常對象,并將其轉(zhuǎn)交給 JVM,該異常對象一般包含 異常名稱、異常描述以及異常發(fā)生時應(yīng)用程序的狀態(tài)。這個 創(chuàng)建異常對象并轉(zhuǎn)交給 JVM 的過程叫做拋出異常。可能有一系列的方法調(diào)用,最終才能進入拋出異常的方法,這一系列方法調(diào)用的有序列表叫做調(diào)用棧。
JVM 沿著調(diào)用棧去查找是否有需要處理異常的代碼,一旦發(fā)現(xiàn)則調(diào)用異常處理代碼。當(dāng) JVM 發(fā)現(xiàn)可以處理異常的代碼時,會將發(fā)生的異常傳遞給它。如果 JVM 未找到能夠處理該異常的代碼塊,就會將其轉(zhuǎn)交給默認的異常處理器(JVM 的一部分),由異常處理器打印出異常信息并終止應(yīng)用程序;



