# **概述**
顧名思義,通俗來講異常就是指,那些發生在我們原本考慮和設定的計劃之外的意外情況。
生活中總是會存在各種突發情況,如果沒有做好準備,就讓人措手不及。
你和朋友約好了明天一起去登山,半道上忽然烏云蔽日,下起了磅礴大雨。這就是所謂的異常情況。
你一下子傻眼了,然后看見朋友淡定的從背包里掏出一件雨衣穿上,淫笑著看著你。這就是對異常的處理。
對于一個OO程序猿來講,所做的工作就是:將需要處理的現實生活中的復雜問題,抽象出來編寫成為程序。
既然現實生活中總是存在著各種突然的異常情況,那么對應其抽象出的代碼,自然也是存在這樣的風險的。
所以常常說:要編寫一個完善的程序并不只是簡簡單單的把功能實現,還要讓程序具備處理在運行中可能出現的各種意外情況的能力。
這就是所謂的異常的使用。
# **體系**

這就是Java當中異常體系的結構構成,從圖中我們可以提取到的信息就是:
1、Java中定義的所有異常類都是內置類Throwable的子類。
2、Java中的異常通常被分為兩大類:Error和Exception:
- 那么顧名思義,Error代表錯誤,Exception代表異常;
- Error用以指明與運行環境相關的錯誤,JVM無法從此類錯誤中恢復,此類異常無需我們處理;
- Exception代表著可以被我們所處理的異常情況,我們需要掌握和使用的,正是該類型。
3、Exception最常見的兩種異常類型分別是:
- IOException:主要是用以處理操作數據流時可能會出現的各種異常情況。
- RuntimeException:指發生在程序運行時期的異常,如數組越界,入參不滿足規范等情況引起的程序異常。
# **工欲善其事,必先利其器**
要理解Java中的異常使用,首先要明白幾個關于異常處理的工具 - 異常處理關鍵字的使用。
1、throw:用以在方法內部拋出指定類型的異常。
~~~
void test(){
if(發生異常的條件){
thorw new Exception("拋出異常");
}
}
~~~
2、throws:用以聲明 一個方法可能發生的異常(通常都是編譯時檢測異常)有哪些。
~~~
void test() throws 異常1,異常2{
//這個方法可能發生異常1,異常2
throw new 異常1();
throw new 異常2();
}
~~~
3、try - catch:另一種處理異常的方式,與throws不同的是,這種方式是指 "捕獲并處理"的方式。
用try語句塊包含可能發生異常的代碼,catch用于捕獲發生的異常,并在catch語句塊中定義對捕獲到的異常的處理方式。
~~~
try {
//可能發生異常的代碼
} catch (要捕獲的異常類型 e) {
//對捕獲到的異常的處理方式
}
~~~
4、finally語句塊:通常都是跟在try或try-catch之后進行使用,與其名字代表的一樣。也就是定義最終的操作。
特點是被try語句塊包含的內容中,是否真的發生了異常。程序最終都將執行finally語句塊當中的內容。
通常用于對資源的釋放操作,例如:通過JDBC連接數據庫等情況。
~~~
try {
//獲取資源
} catch (要捕獲的異常類型 e) {
//對捕獲到的異常的處理方式
}finally{
//釋放資源
}
~~~
# **趣解異常的實際使用**
了解Java中異常類的實際應用之前,應當先了解兩個概念,用以對最常用的異常做一個分類:
1、編譯時被檢測異常:
只要是Exception和其子類都是,除了特殊子類RuntimeException體系。
所謂的編譯時被檢測異常也就是指在程序的編譯器就會進行檢測的異常分類。
也就是說,如果一個方法拋出了一個編譯時檢測異常,Java則要求我們必須進行處理。
既:通過throws對異常進行聲明處理 或是 通過try-catch對異常進行捕獲處理。
如果程序編譯時檢測到該類異常沒有被進行任何處理,那么編譯器則會報出一個編譯錯誤。
~~~
public class Test{
public static void main(String[] args) {
try {
Class clazz = Class.forName("Java");
System.out.println(clazz.getName());
} catch (ClassNotFoundException e) {
System.out.println("沒有找到該類");
}
}
}
~~~
上面代碼中的ClassNotFoundException就是一種編譯時檢測異常,這個異常是由Class類當中的forName方法所拋出并聲明的。
如果我們在使用該方法時沒有對異常進行處理:聲明或捕獲,那么該程序就會編譯失敗。
通過這個例子想要說明的是:編譯時被檢測異常通常都是指那些“可以被我們預見”的異常情況。
正例如:我們通過Class.forName是想要獲取指定類的字節碼文件對象,所以我們自然也可以預見可能會存在:
與我們傳入的類名參數所對應的類字節碼文件對象不存在,查找不到的情況。
既然這種意外情況是可以被預見的,那自然就應該針對其制定一些應對方案。
2、編譯時不檢測異常(運行時異常):
就是指Exception下的子類RuntimeException和其子類。
通常這種問題的發生,會導致程序功能無法繼續、運算無法進行等情況發生;
但這類異常更多是因為調用者的原因或者引發了內部狀態的改變而導致的。
所以針對于這種異常,編譯器不要求我們處理,可以直接編譯通過。
而在運行時,讓調用者調用時的程序強制停止,從而讓調用者對自身的代碼進行修正。
曾經看到過一道面試題:列出你實際開發中最常見的五個運行時異常,就我自己而言,如果硬要說出五個,那可能是:
NullPointerException(空指針異常)、IndexOutOfBoundsException(角標越界異常)、ArithmeticException(異常運算條件異常)
ClassCastException(類型轉換異常)、IllegalArgumentException(非法參數異常)
~~~
public class Test{
public static void main(String[] args) {
division(5, 0);
}
static int division(int a ,int b){
return a/b;
}
}
/*
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.tsr.j2seoverstudy.base.Test.division(Test.java:31)
at com.tsr.j2seoverstudy.base.Test.main(Test.java:28)
*/
~~~
上面的例子就報出了運行時異常:ArithmeticException。因為我們將非法的被除數0作為參數傳遞給了除法運算的函數內。
同時也可以看到,雖然“division”方法可能引發異常,但因為是運行時異常,所以即使不做任何異常處理,程序任然能夠通過編譯。
但當該類型的異常真的發生的時候,調用者運行的程序就會直接停止運行,并輸出相關的異常信息。
通過自定義異常理解檢測異常和非檢測異常
前面我們說到的都是Java自身已經封裝好提供給我們的一些異常類。由此我們可以看到,秉承于“萬物皆對象”的思想,Java中的異常實際上也是一種對象。
所以自然的,除了Java本身提供的異常類之外,我們也可以根據自己的需求定義自己的異常類。
這里我想通過比較有趣的簡單的自定義異常,結合自己的理解,總結一下Java當中檢測異常和非檢測異常的使用。
**1、編譯時檢測異常**
對于編譯時異常,我的理解就是:所有你**可以預見**、**并且能夠做出應對**的**意外狀況**,都應該通過**編譯時檢測異常**的定義的方式進行處理。
舉個例子來說:假定我們開了一家小餐館,除開正常營業的流程之外。自然可能發生一些意外狀況,例如:
菜里不小心出現了蟲子,出現了頭發;或者是餐館突然停電之類的狀況。這些狀況是每個經營餐館的人事先都應該考慮到的情況。
既然我們已經考慮到了這些意外情況發生的可能性,那么自然就應該針對于這些狀況做出應對的方案。所以代碼可能是這樣的:
1、首先,定義兩個編譯時檢測異常類,菜品異常和停電異常:
~~~
package com.tsr.j2seoverstudy.exception_demo;
/*
* 菜品異常
*/
public class DishesException extends Exception{
public DishesException() {
super("菜品有問題..");
}
}
package com.tsr.j2seoverstudy.exception_demo;
/*
* 停電異常
*/
public class PowerCutException extends Exception{
PowerCutException(){
super("停電異常..");
}
}
~~~
2、然后在餐廳類當中,對異常作出處理:
~~~
package com.tsr.j2seoverstudy.exception_demo;
public class MyRestaurant {
private static String sicuation;
static void doBusiness() throws DishesException, PowerCutException{
if(sicuation.equals("菜里有蟲") ||sicuation.equals("菜里有頭發")){
throw new DishesException();
}
else if(sicuation.equals("停電")){
throw new PowerCutException();
}
}
public static void main(String[] args) {
try {
doBusiness();
} catch (DishesException e) {
//換一盤菜或退款
} catch (PowerCutException e) {
//啟動自備發電機
}
}
}
~~~
1、我們已經說過了菜品出現問題和停電之類的意外情況都是我們可以預見的,所以我們首先定義了兩個編譯時檢測異常類用以代表這兩種意外情況。
2、然后我們在餐廳類當中的營業方法當中做出了聲明,如果出現“菜里有蟲”或“菜里有頭發的問題”,我們就用thorw拋出一個菜品異常;如果“停電”,就拋出停電異常。
3、但是,由于我們拋出這一類異常是因為想告知餐廳的相關人員,在餐廳營業后,可能會出現這些意外情況。所以還應當通過throws告訴他們:營業可能會出現這些意外情況。
4、餐廳相關人員接到了聲明。于是制定了方案,當餐廳開始營業后。如果出現了菜品異常,請為客人換一盤菜或退款;如果出現停電異常,請啟動店里自備的發電機。
**2、運行時異常**
對于運行時異常的使用,我個人覺得最常用的情況有兩種:
第一、編譯時檢測異常用于定義那些我們可以提供“友好的解決方案”的情況。那么針對于另外一些狀況,可能是我們無法很好的進行解決的。
遇到這種情況,我們可能希望采取一些“強制手段”,那就是直接讓你的程序停止運行。這時,就可以使用運行時異常。
第二、如果對異常處理后,又引發一連串的錯誤的“連鎖反應”的時候。
我們先來看一下第一種使用使用情況是怎么樣的。例如說:
我們在上面的餐廳的例子中,餐廳即使出現菜品異常或停電異常這一類意外情況。
但針對于這一類的意外情況,我們是能夠提供較為妥善的解決方案的。
而通過我們提供的針對于這些異常情況的解決方案進行處理之后,餐廳照常營業,顧客接著用餐(程序依舊能夠正常運行)。
但還有一種情況,可能無論我們怎么樣友好的嘗試進行解決,都難以讓顧客滿意。這種顧客就是傳說中被稱為“**砸場子**”的顧客。
針對于這種情況,我們可能就要采取更為“強硬的措施”了。例如直接報警把他帶走(不讓程序繼續運行了),這就是所謂的運行時異常:
~~~
package com.tsr.j2seoverstudy.exception_demo;
//砸場子異常
public class HitException extends RuntimeException {
HitException() {
super("草,砸場子,把你帶走! ");
}
}
~~~
這時,餐館類被修改為:
~~~
package com.tsr.j2seoverstudy.exception_demo;
public class MyRestaurant {
private static String sicuation;
static void doBusiness() throws DishesException, PowerCutException {
if (sicuation.equals("菜里有蟲") || sicuation.equals("菜里有頭發")) {
throw new DishesException();
} else if (sicuation.equals("停電")) {
throw new PowerCutException();
} else if (sicuation.equals("砸場子")) {
throw new HitException();
}
}
public static void main(String[] args) {
try {
sicuation = "砸場子";
doBusiness();
} catch (DishesException e) {
// 換一盤菜或退款
} catch (PowerCutException e) {
// 啟動自備發電機
}
}
}
~~~
于是運行該程序,就會出現:

可以看到出現該運行時異常,程序將直接被終止運行,砸場子的人直接被警察帶走了。
那么接下來,我們就可以來看看第二種使用情況了,什么是所謂的“引發連鎖效應的錯誤”。
舉個例子來說,以我們上面用到的“被除數為0”的異常情況。你可能會思考:傳入的被除數為0,這樣的情況我們是可以考慮到的。
并且我們也可以針對這樣的錯誤給出對應的措施。那Java為什么不將這樣的異常定義為編譯時檢測異常呢?
那么我不妨假設ArithmeticException就是編譯時檢測異常,所以我們必須對其作出處理,那么可能出現這樣的代碼:
~~~
public class Test {
?public static void main(String[] args) {
??? ?System.out.println("5除以0的結果為:" + division(5, 0));
?}
?static int division(int a, int b) {
??? ?int num = 0;
??? ?try{
??? ?num = a/b;
??? ?}catch (ArithmeticException e) {
??? ??? ?num = -1;
??? ?}
??? ?
??? ?return num;
?}
}
}
~~~
我們提供了一個進行除法運算的方法,針對于傳入的被除數為0的異常情況,我們也給出了自己的解決方案:
如果傳入的被除數為0,就返回負數“-1”,“-1”就代表這個運算出錯了。
于是這時有一個調用者,剛好調用了我們的除法運算方法計算“5除以0的結果”,理所當然的,他得到的結果為:
“5除以0的結果為-1”。好了,這下叼了,這哥們立馬拿著這個運算結果,去向他的朋友炫耀:
你們都是2B吧,算不出5除以0等于多少是吧?告訴你們,等于-1。于是,在朋友的眼中,他成2B了。
- 前言
- 第一個專欄《重走J2SE之路》,你是否和我有一樣的困擾?
- 磨刀不誤砍材工 - 環境搭建(為什么要配置環境變量)
- 磨刀不誤砍材工 - Java的基礎語言要素(定義良好的標示符)
- 磨刀不誤砍材工 - Java的基礎語言要素(關鍵字)
- 磨刀不誤砍材工 - Java的基礎語言要素(注釋-生成你自己的API說明文檔)
- 磨刀不誤砍材工 - Java的基礎語言要素(從變量/常量切入,看8種基本數據類型)
- 磨刀不誤砍材工 - Java的基礎語言要素(運算符和表達式的應用)
- 磨刀不誤砍材工 - Java的基礎語言要素(語句-深入理解)
- 磨刀不誤砍材工 - Java的基礎語言要素(數組)
- 換一個視角看事務 - 用"Java語言"寫"作文"
- 牛刀小試 - 淺析Java的繼承與動態綁定
- 牛刀小試 - 詳解Java中的接口與內部類的使用
- 牛刀小試 - 趣談Java中的異常處理
- 牛刀小試 - 詳解Java多線程
- 牛刀小試 - 淺析Java集合框架的使用
- 牛刀小試 - Java泛型程序設計
- 牛刀小試 - 詳細總結Java-IO流的使用