參考編寫 https://www.imooc.com/article/275564
Java中異常的分類
所有異常,都繼承自java.lang.Throwable類。
Throwable有兩個直接子類,Error類和Exception類。
Throwable 是所有異常類型的基類,Throwable 下一層分為兩個分支,Error 和 Exception.
Error 和 Exeption
- Error
Error 描述了 JAVA 程式運作時系統的内部錯誤,通常比較嚴重,除了通知使用者和盡力使應用程式安全地終止之外,無能為力,應用程式不應該嘗試去捕獲這種異常。通常為一些虛拟機異常,如 StackOverflowError 等。
- Exception
Exception 類型下面又分為兩個分支,一個分支派生自 RuntimeException,這種異常通常為程式錯誤導緻的異常;另一個分支為非派生自 RuntimeException 的異常,這種異常通常是程式本身沒有問題,由于像 I/O 錯誤等問題導緻的異常,每個異常類用逗号隔開。
Exception則可使從任何标準Java庫的類方法,自己的方法以及運作時任何異常中抛出來的基類型。
異常可分為執行異常(RuntimeException)和檢查異常(Checked Exceptions)兩種:
受查異常和非受查異常
非受檢異常
RuntimeExceptin
RuntimeException在預設情況下會得到自動處理。是以通常用不着捕獲RuntimeException,但在自己的封裝裡,也許仍然要選擇抛出一部分RuntimeException。
RuntimeException是那些可能在 Java 虛拟機正常運作期間抛出的異常的超類。可能在執行方法期間抛出但未被捕獲的RuntimeException的任何子類都無需在throws子句中進行聲明。(java api)
它是UncheckedExcepiton。
Java.lang.ArithmeticException
Java.lang.ArrayStoreExcetpion
Java.lang.ClassCastException
Java.lang.EnumConstantNotPresentException
Java.lang.IllegalArgumentException
Java.lang.IllegalThreadStateException
Java.lang.NumberFormatException
Java.lang.IllegalMonitorStateException
Java.lang.IllegalStateException
Java.lang.IndexOutOfBoundsException
Java.lang.ArrayIndexOutOfBoundsException
Java.lang.StringIndexOutOfBoundsException
Java.lang.NegativeArraySizeException
Java.lang.NullPointerException
Java.lang.SecurityException
Java.lang.TypeNotPresentException
Java.lang.UnsupprotedOperationException
受檢異常
除了RuntimeException以外的異常,都屬于checkedException,它們都在java.lang庫内部定義。Java編譯器要求程式必須捕獲或聲明抛出這種異常。
一個方法必須通過throws語句在方法的聲明部分說明它可能抛出但并未捕獲的所有CheckedException。
Java.lang.ClassNotFoundException
Java.lang.CloneNotSupportedException
Java.lang.IllegalAccessException
Java.lang.InterruptedException
Java.lang.NoSuchFieldException
Java.lang.NoSuchMetodException
受查異常會在編譯時被檢測。如果一個方法中的代碼會抛出受查異常,則該方法必須包含異常處理,即 try-catch 代碼塊,或在方法簽名中用 throws 關鍵字聲明該方法可能會抛出的受查異常,否則編譯無法通過。如果一個方法可能抛出多個受查異常類型,就必須在方法的簽名處列出所有的異常類。
通過 throws 關鍵字聲明可能抛出的異常
interfaceUser{
//修改使用者名,抛出安全異常
publicvoid changePassword() throws MySecurityExcepiton;
}
或者
private static void readFile(String filePath) throws IOException {
File file = new File(filePath);
String result;
BufferedReader reader = new BufferedReader(new FileReader(file));
while((result = reader.readLine())!=null) {
System.out.println(result);
}
reader.close();
}
try-catch 處理異常
publicstatic void main(String[] args){
try{
doStuff();
}catch(Exceptione){
e.printStackTrace();
}
}
或者
private static void readFile(String filePath) {
File file = new File(filePath);
String result;
BufferedReader reader;
try {
reader = new BufferedReader(new FileReader(file));
while((result = reader.readLine())!=null) {
System.out.println(result);
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
異常的抛出與捕獲
直接抛出異常
通常,應該捕獲那些知道如何處理的異常,将不知道如何處理的異常繼續傳遞下去。傳遞異常可以在方法簽名處使用 throws 關鍵字聲明可能會抛出的異常。
private static void readFile(String filePath) throws IOException {
File file = new File(filePath);
String result;
BufferedReader reader = new BufferedReader(new FileReader(file));
while((result = reader.readLine())!=null) {
System.out.println(result);
}
reader.close();
}
封裝異常再抛出
有時我們會從 catch 中抛出一個異常,目的是為了改變異常的類型。多用于在多系統內建時,當某個子系統故障,異常類型可能有多種,可以用統一的異常類型向外暴露,不需暴露太多内部異常細節。
private static void readFile(String filePath) throws MyException {
try {
// code
} catch (IOException e) {
MyException ex = new MyException("read file failed.");
ex.initCause(e);
throw ex;
}
}
捕獲異常
在一個 try-catch 語句塊中可以捕獲多個異常類型,并對不同類型的異常做出不同的處理
private static void readFile(String filePath) {
try {
// code
} catch (FileNotFoundException e) {
// handle FileNotFoundException
} catch (IOException e){
// handle IOException
}
}
同一個 catch 也可以捕獲多種類型異常,用 | 隔開
private static void readFile(String filePath) {
try {
// code
} catch (FileNotFoundException | UnknownHostException e) {
// handle FileNotFoundException or UnknownHostException
} catch (IOException e){
// handle IOException
}
}
自定義異常
習慣上,定義一個異常類應包含兩個構造函數,一個無參構造函數和一個帶有較長的描述資訊的構造函數(Throwable 的 toString 方法會列印這些詳細資訊,調試時很有用)
public class MyException extends Exception {
public MyException(){ }
public MyException(String msg){
super(msg);
}
// ...
}
try-catch-finally
當方法中發生異常,異常處之後的代碼不會再執行,如果之前擷取了一些本地資源需要釋放,則需要在方法正常結束時和 catch 語句中都調用釋放本地資源的代碼,顯得代碼比較繁瑣,finally 語句可以解決這個問題。
private static void readFile(String filePath) throws MyException {
File file = new File(filePath);
String result;
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(file));
while((result = reader.readLine())!=null) {
System.out.println(result);
}
} catch (IOException e) {
System.out.println("readFile method catch block.");
MyException ex = new MyException("read file failed.");
ex.initCause(e);
throw ex;
} finally {
System.out.println("readFile method finally block.");
if (null != reader) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
調用該方法時,讀取檔案時若發生異常,代碼會進入 catch 代碼塊,之後進入 finally 代碼塊;若讀取檔案時未發生異常,則會跳過 catch 代碼塊直接進入 finally 代碼塊。是以無論代碼中是否發生異常,fianlly 中的代碼都會執行。
若 catch 代碼塊中包含 return 語句,finally 中的代碼還會執行嗎?将以上代碼中的 catch 子句修改如下
catch (IOException e) {
System.out.println("readFile method catch block.");
return;
}
調用 readFile 方法,觀察當 catch 子句中調用 return 語句時,finally 子句是否執行
可見,即使 catch 中包含了 return 語句,finally 子句依然會執行。若 finally 中也包含 return 語句,finally 中的 return 會覆寫前面的 return.
try-with-resource
上面例子中,finally 中的 close 方法也可能抛出 IOException, 進而覆寫了原始異常。JAVA 7 提供了更優雅的方式來實作資源的自動釋放,自動釋放的資源需要是實作了 AutoCloseable 接口的類。
private static void tryWithResourceTest(){
try (Scanner scanner = new Scanner(new FileInputStream("c:/abc"),"UTF-8")){
// code
} catch (IOException e){
// handle exception
}
}
try 代碼塊退出時,會自動調用 scanner.close 方法,和把 scanner.close 方法放在 finally 代碼塊中不同的是,若 scanner.close 抛出異常,則會被抑制,抛出的仍然為原始異常。被抑制的異常會由 addSusppressed 方法添加到原來的異常,如果想要擷取被抑制的異常清單,可以調用 getSuppressed 方法來擷取。
常見面試題
-
Error 和 Exception 差別是什麼?
Error 類型的錯誤通常為虛拟機相關錯誤,如系統崩潰,記憶體不足,堆棧溢出等,編譯器不會對這類錯誤進行檢測,JAVA 應用程式也不應對這類錯誤進行捕獲,一旦這類錯誤發生,通常應用程式會被終止,僅靠應用程式本身無法恢複;
Exception 類的錯誤是可以在應用程式中進行捕獲并處理的,通常遇到這種錯誤,應對其進行處理,使應用程式可以繼續正常運作。
-
運作時異常和一般異常差別是什麼?
編譯器不會對運作時異常進行檢測,沒有 try-catch,方法簽名中也沒有 throws 關鍵字聲明,編譯依然可以通過。如果出現了 RuntimeException, 那一定是程式員的錯誤。
一般一場如果沒有 try-catch,且方法簽名中也沒有用 throws 關鍵字聲明可能抛出的異常,則編譯無法通過。這類異常通常為應用環境中的錯誤,即外部錯誤,非應用程式本身錯誤,如檔案找不到等。
-
NoClassDefFoundError 和 ClassNotFoundException 差別是什麼?
NoClassDefFoundError 是一個 Error 類型的異常,是由 JVM 引起的,不應該嘗試捕獲這個異常。引起該異常的原因是 JVM 或 ClassLoader 嘗試加載某類時在記憶體中找不到該類的定義,該動作發生在運作期間,即編譯時該類存在,但是在運作時卻找不到了,可能是變異後被删除了等原因導緻;
ClassNotFoundException 是一個受查異常,需要顯式地使用 try-catch 對其進行捕獲和處理,或在方法簽名中用 throws 關鍵字進行聲明。當使用 Class.forName, ClassLoader.loadClass 或 ClassLoader.findSystemClass 動态加載類到記憶體的時候,通過傳入的類路徑參數沒有找到該類,就會抛出該異常;另一種抛出該異常的可能原因是某個類已經由一個類加載器加載至記憶體中,另一個加載器又嘗試去加載它。
-
JVM 是如何處理異常的?
在一個方法中如果發生異常,這個方法會建立一個異常對象,并轉交給 JVM,該異常對象包含異常名稱,異常描述以及異常發生時應用程式的狀态。建立異常對象并轉交給 JVM 的過程稱為抛出異常。可能有一系列的方法調用,最終才進入抛出異常的方法,這一系列方法調用的有序清單叫做調用棧。
JVM 會順着調用棧去查找看是否有可以處理異常的代碼,如果有,則調用異常處理代碼。當 JVM 發現可以處理異常的代碼時,會把發生的異常傳遞給它。如果 JVM 沒有找到可以處理該異常的代碼塊,JVM 就會将該異常轉交給預設的異常處理器(預設處理器為 JVM 的一部分),預設異常處理器列印出異常資訊并終止應用程式。
-
throw 和 throws 的差別是什麼?
throw 關鍵字用來抛出方法或代碼塊中的異常,受查異常和非受查異常都可以被抛出。
throws 關鍵字用在方法簽名處,用來辨別該方法可能抛出的異常清單。一個方法用 throws 辨別了可能抛出的異常清單,調用該方法的方法中必須包含可處理異常的代碼,否則也要在方法簽名中用 throws 關鍵字聲明相應的異常。
- 常見的 RuntimeException 有哪些?
- ClassCastException(類轉換異常)
- IndexOutOfBoundsException(數組越界)
- NullPointerException(空指針)
- ArrayStoreException(資料存儲異常,操作數組時類型不一緻)
- 還有IO操作的BufferOverflowException異常