
前言
在日常的開發以及平時的學習練習中,異常相信對于大家來講并不陌生,但是對于異常的具體使用、底層實現以及分類等等可能并不是很了解。今天我就抽出了一點時間系統的整理了異常的各個知識點,希望能夠幫助到大家對于Java 異常的理解與學習。
正文
一、異常的定義
異常:是一個在程序執行期間發生的事件,它中斷正在執行程序的正常指令流。我自己的的理解是異常通常是一種人為規定的不正常的現象,通常是寫的程序產生了一些不符合規范的事情,而且異常一旦產品,程序直接掛掉。
二、異常的分類
通過我自己整理的這一張繼承關系結構圖,我們可以很清晰的看到異常這個龐大體系的繼承關系。

其中我們可以看到 Exception類 下面又細分了兩個分支(子類),分別是運行時異常(也稱非檢查異常[ Unchecked Exception ])和非運行時異常(也稱檢查異常[ Checked Exception ]),需要注意的是 Error 也是屬于非檢查異常。
1、運行時異常
運行時異常也稱非檢查異常,指的是 Error 和 RuntimeException 這兩種。既然是叫運行時異常,那肯定是在程序運行期間才可能被檢查出的異常,那自然編譯器在編譯的時候是不會檢查此類異常的,并且不要求處理該類異常,所以此類異常也叫非檢查異常(或者不檢查異常),這一類異常一般是由程序邏輯錯誤引起的,在程序中可以選擇捕獲處理,也可以不處理。
下圖我整理了一些常見的運行時異常,如下表格所示:

2、非運行時異常
非運行時異常指的是除開 Error 和 RuntimeException 極其子類之外的異常。既然叫做非運行時異常,顧名思義,就是不是在程序運行期間檢查的異常,既然不是在程序運行期間檢查,那就是編譯器在編譯期間就會檢查此類異常,所以此類異常也叫檢查異常(或者叫編譯時異常)。此類異常在程序運行過程中極有可能產生問題,如果程序中出現此類異常(比如說 IOException ),必須對該異常進行處理,要么使用 try-catch 捕獲,要么使用 throws語句拋出,否則編譯不通過。
下圖我整理了一些常見的非運行時異常,如下表格所示:

三、異常的處理
既然異常產生了,就需要我們去處理。處理異常不是讓異常消失了,處理異常指的是處理掉異常之后,后續的代碼不會因為此異常而終止執行,代碼中的異常處理其實是對非檢查異常也就是運行時異常的處理。
異常的處理有兩種方式:捕獲異常或者拋出異常這兩種方式。
1、捕獲異常,通過try…catch…finally(其中finally可有可無)語句塊來處理
其中 try/catch 代碼塊放在異常可能發生的地方,把可能會發生異常的代碼直接放到 try 塊里面,如果在 try 塊里面有異常發生,try 塊代碼里面后面的代碼就不執行了,直接匹配 catch 里面的異常,如果沒有異常發生, 就直接執行完try里面的代碼,catch 里面的代碼不執行。
try…catch 語法結構如下:
try{//程序代碼
}catch (Exception e){}
其中 try 不能單獨出現,后面必須添加 catch 或者 finally ,意思就是要么 try…catch,要么 try…finally,如下:
//寫法1
try{//程序代碼
}catch (Exception e){}
//寫法2
try{//程序代碼
}finally {}//寫法3
try{//程序代碼
}catch (Exception e){} finally {}
其中,catch 可以有多個存在
//程序代碼
}catch (ArrayIndexOutOfBoundsException e1){//程序代碼
}catch (IndexOutOfBoundsException e2){//程序代碼
}catch (RuntimeException e3){//程序代碼
}
其中 catch 語句包含要捕獲異常類型的聲明。當保護代碼塊中發生一個異常時,try 后面的 catch 塊就會被檢查。如果發生的異常包含在 catch 塊中,異常會被傳遞到該 catch 塊,這和傳遞一個參數到方法是一樣。如果catch 有多個,捕獲的異常需要從小到大捕獲。
關于try 、catch 、finally 關鍵字的執行順序
我們先來看看下面代碼的執行:
public static void main(String[] args) {System.out.println("try開始");String str = null;int length = str.length();System.out.println("try完畢");
}
面代碼的執行結果是:
try開始
Exception in thread “main” java.lang.NullPointerException
at com.example.javabasic.javabasic.ExceptionAndError.ExceptionTest.main(ExceptionTest.java:12)
再來看看下面代碼的執行:
public static void main(String[] args) {System.out.println("程序最早開始執行");try{System.out.println("try開始");String str = null;int length = str.length();System.out.println("try完畢");} catch (ArrayIndexOutOfBoundsException e1){System.out.println("數組下標越界異常");}System.out.println("產生異常之后的所有程序");
}
上面代碼的執行結果是:
Exception in thread “main” java.lang.NullPointerException
try開始
at com.example.javabasic.javabasic.ExceptionAndError.ExceptionTest.main(ExceptionTest.java:14)
再來看看下面的代價的執行:
System.out.println("程序最早開始執行");try{System.out.println("try開始");String str = null;int length = str.length();System.out.println("try完畢");} catch (NullPointerException e1){System.out.println("空指針異常");}System.out.println("產生異常之后的所有程序");
}
上面代碼的執行結果是:
程序最早開始執行
try開始
空指針異常
產生異常之后的所有程序
通過上面三個例子,其實我們可以發現如果程序中如果有代碼會出現異常,當你沒有手動進行捕獲的時候,當異常發生,代碼直接中斷,后面的代碼也不會執行,當然這是顯而易見的;如果你捕獲了與之匹配的異常,那么在try塊里面發生異常的后面的代碼不會執行,位于try塊之后的代碼都會正常執行;如果你捕獲了異常,但是發生的異常與你捕獲的異常不匹配,程序也會立刻終止,后面的代碼都不會執行。
try、catch、finally關鍵字的執行順序的優先級是,try塊中的代碼會被執行,如果出現異常則執行最匹配的catch塊。但是無論是否有匹配的catch塊,只要有相關的finally塊,那么finally塊都會被執行,雖然finally塊的優先級最低。
2、拋出異常,也就是在具體位置不處理,直接拋出,通過throws/throw到上層再進行處理。
當我們遇到可能發生的異常的時候,如果我們覺得自己手動處理太麻煩,自己不想處理怎么辦呢?方法總比困難多,我們可以將異常拋出去,換而言之,就是丟給別人去處理,其中拋出異常有兩種方式throws和throw。
第一種方式:throws聲明異常
運用于方法聲明之上,用于表示當前方法不處理異常,而是提醒該方法的調用者來處理異常,比如下面兩個代碼塊
public static void main(String[] args) {try {testException();} catch (InterruptedException e) {e.printStackTrace();}
}private static void testException() throws InterruptedException {Thread.sleep(100);
}
或者:
public static void main(String[] args) throws InterruptedException {testException();
}
private static void testException() throws InterruptedException {Thread.sleep(100);
}
需要注意的是如果是主函數main方法調用了會拋出異常的方法,那么一般都是不建議主方法再拋出去的(如果主方法拋出去,就相當于拋給虛擬機處理了,然而虛擬機是不會處理的。),而是在主方法里進行捕獲異常,再進行處理。
其中,throws拋出異常的時候可以拋出多個異常,通過逗號隔開,例如下方代碼:
private static void testException() throws InterruptedException, ArrayIndexOutOfBoundsException, ArithmeticException{Thread.sleep(100);
}
第二種方式:throw拋出異常
用在方法內,用來拋出一個異常對象,將這個異常對象傳遞到調用者處,并結 束當前方法的執行。
四、自定義異常
Jdk 提供了很多異常,其實基本都夠我們平時的日常使用了,但是,當 Jdk 提供好的這些所有異常都不能描述或者滿足我的問題的時候,我們其實可以自己定義一個異常來進行描述自己問題的異常,也就是自定義異常。自定義異常都必須是 Throwable 的子類,也就是:
繼承 Throwable
繼承 Exception
繼承 RuntimeException
那么到底繼承哪一個呢?看我們的需要,
如果希望寫一個檢查異常類,則需要繼承 Exception 類或者 Throwable 類,一般繼承 Exception 類。
如果希望寫一個運行時異常類(也就是非檢查異常),則需要繼承 RuntimeException 類。
在平時開發中以及在項目上,一般都是選擇繼承 RuntimeException 類的,例如下面我定義一個自定義運行時異常:
/**
* 自定義運行時異常* @Date: 2020/4/21 9:54*/
public class MyRuntimeException extends RuntimeException{public MyRuntimeException(){}//如果想對自定義異常進行描述public MyRuntimeException(String message){super(message);}
}
測試一下自定義異常:
public static void main(String[] args){testMyRuntimeException();
}
/*** 自定義異常*/
private static void testMyRuntimeException() {throw new MyRuntimeException("我的自定義異常");
}
執行結果為:

五、錯誤
Error 意思就是錯誤,Error 表示系統致命的錯誤,Error 是程序中無法處理的錯誤,表示運行應用程序中出現了嚴重的錯誤,通常是一些物理性的錯誤,這些錯誤是不可查的,因為它們在應用程序的控制和處理能力之外,而且絕大多數是程序運行時不允許出現的狀況。Error 類對象由 Java 虛擬機生成并拋出,大多數錯誤與代碼編寫者所執行的操作無關。錯誤比如 JVM 虛擬機本身出現了問題、比如 StackOverFlowError 棧內存溢出、OutOfMemoryError 堆內存溢出等。在 Java 中,錯誤通過 Error 的子類描述。