Java基礎系列-Exception異常處理


原創文章,轉載請標注出處:《Java基礎系列-Exception異常處理》


一、概述

Java代碼中的異常處理是非常重要的一環,從代碼中可以看到,它的使用已經和業務邏輯緊密的結合在一起,部分業務邏輯還是依靠異常來完成的,更多的時候進行異常處理可以完善邏輯,避免可能的出錯,規避小錯誤引發的大停頓。

在一般的項目之中,都會自定義運行時異常,用以適應項目的需要,這種異常可被捕捉,也可不被捕捉,它們不會導致整個系統掛掉,但是很多情況下,不捕捉處理就會導致業務出錯。

在這里我們模擬幾種情況,點明異常捕捉的使用時機。

二、情況分析

先來看沒有任何處理的代碼:

public class ExceptionTests01 {

    public static void main(String[] args) {
        System.out.println("---1---");
        invoke();
        System.out.println("---2---");
        
    }
    
    public static void invoke(){
        System.out.println("---11---");
        int i = 1/0;
        System.out.println("---12---");
    }
}

其執行結果如下:

---1---
---11---
Exception in thread "main" java.lang.ArithmeticException: / by zero
    at com.donghao.test1.ExceptionTests01.invoke(ExceptionTests01.java:14)
    at com.donghao.test1.ExceptionTests01.main(ExceptionTests01.java:7)

解析:main方法調用invoke方法,在執行到第12行時出錯,產生算法異常,此時由于無任何異常處理手段,結果就是,程序執行到這里之后直接中斷,執行結果中輸出的異常堆棧信息是Java內部默認的異常處理機制處理的結果。

改造一:我們在invoke方法內部加上異常捕捉機制,代碼如下:

public class ExceptionTests01 {

    public static void main(String[] args) {
        System.out.println("---1---");
        invoke();
        System.out.println("---2---");
        
    }
    
    public static void invoke(){
        try{
            System.out.println("---11---");
            int i = 1/0;
        }catch(Exception e){
            System.out.println("---12---");
        }
        System.out.println("---13---");
    }
}

執行結果:

---1---
---11---
---12---
---13---
---2---

結果解析:我們在invoke方法的執行代碼外圍添加異常捕捉代碼,捕捉Exception異常,這是所有異常的基類,當然也包含這里的算法異常,那么這個捕捉機制就會將1/0產生的異常捕捉到,捕捉到這個異常之后,就會跳轉到catch語句塊中執行針對這個異常的處理語句,執行完成后,會繼續執行try...catch語句塊之后的代碼,這樣的好處顯而易見,一處的小錯誤并不會阻擋整個代碼的持續執行,當然如果是嚴重問題,我們確實需要暫停執行的,就不能使用這種情況,使用之前的代碼就行,所以異常處理機制的執行時機完全是由項目的業務情況而定的,是非常靈活的,不是固定的死板的。我們要根據實際的業務場景來合理的使用才是正理。

改造二:我們在main方法中也添加異常捕捉,代碼如下:

public class ExceptionTests01 {

    public static void main(String[] args) {
        try{
            System.out.println("---1---");
            invoke();
        }catch(Exception e){
            System.out.println("---2---");
        }
        System.out.println("---3---");
    }
    
    public static void invoke(){
        try{
            System.out.println("---11---");
            int i = 1/0;
        }catch(Exception e){
            System.out.println("---12---");
        }
        System.out.println("---13---");
    }
}

其執行結果如下:

---1---
---11---
---12---
---13---
---3---

結果幾乎與之前的完全一致,不同之處在于2沒有輸出,一是我改變了2的輸出位置,并新增了3的輸出,現在3相當于之前2的位置,2沒有輸出的原因是因為任何一個異常只能被捕捉一次,一旦被捕捉處理,那么之后就不會再次被捕捉,即使我在main方法中將異常類型改成算法異常,也不會捕捉到,異常只會被距離它最近的包含該異常的異常捕捉到,這里的兩個異常捕捉其實就是一個嵌套的異常捕捉,而且二者捕捉的異常還是一致的,一般情況我們是不會這么使用的,因為毫無意義。但不是說它就完全不會出現,可能invoke中的代碼較長,會有多處異常情況出現,我們可以在main方法中統一捕捉,而invoke中的異常捕捉只針對單一異常,表示這個異常的出現不會影響invoke方法后面的代碼執行,沒有異常捕捉的代碼一旦出現異常就會中斷其后方所有代碼的執行(同一代碼塊內),這個異常會被main方法中的異常捕捉機制捕捉到并執行處理,這樣main方法中調用invoke之后的代碼仍然可以執行,不會被調用發生異常而中斷。

但是如果我們再將invoke方法中的異常捕捉改變如下:

public class ExceptionTests01 {

    public static void main(String[] args) {
        try{
            System.out.println("---1---");
            invoke();
        }catch(Exception e){
            System.out.println("---2---");
        }
        System.out.println("---3---");
    }
    
    public static void invoke(){
        try{
            System.out.println("---11---");
            int i = 1/0;
        }catch(NullPointerException e){
            System.out.println("---12---");
        }
        System.out.println("---13---");
    }
}

執行結果發生了變化:

---1---
---11---
---2---
---3---

為什么呢?正是因為我們更改了invoke方法中捕捉的異常類型,之前是異常基類型Exception,現在改成具體的空指針異常,那么這個異常捕捉就只能捕捉空指針異常,它對此處發生的算法異常就會視而不見(由于異常類型的不對口,那么這個異常捕捉相當于沒有添加,可以想象成沒有異常捕捉的情況),這樣就導致invoke方法中在1/0發生異常之后的所有代碼全部不會執行,而我們在main方法中新增的異常捕獲卻能捕獲到這種算法異常,所以12和13都不會輸出,而是在異常發生后直接就跳轉到main方法中進行異常捕捉,執行catch語句塊處理語句輸出2,然后是3。

再看一個特殊的情況:

public class ExceptionTests01 {

    public static void main(String[] args) {
        System.out.println("---1---");
        invoke();
        System.out.println("---2---");
    }
    
    public static void invoke(){
        for(int i = -2;i < 3;i++){
            System.out.println("---11---");
            System.out.println("12/"+i+"="+12/i);
            System.out.println("---12---");
        }
        System.out.println("---13---");
    }
}

invoke方法中是一個循環輸出,當第12行發生異常時,循環中斷,默認的異常處理機制打印異常堆棧:

---1---
---11---
12/-2=-6
---12---
---11---
12/-1=-12
---12---
---11---
Exception in thread "main" java.lang.ArithmeticException: / by zero
    at com.donghao.test1.ExceptionTests01.invoke(ExceptionTests01.java:14)
    at com.donghao.test1.ExceptionTests01.main(ExceptionTests01.java:7)

改造一:在invoke方法的for循環外部添加try...catch:

public class ExceptionTests01 {

    public static void main(String[] args) {
        System.out.println("---1---");
        invoke();
        System.out.println("---2---");
    }
    
    public static void invoke(){
        try{
            for(int i = -2;i < 3;i++){
                System.out.println("---11---");
                System.out.println("12/"+i+"="+12/i);
                System.out.println("---12---");
            }
            System.out.println("---13---");
        }catch(Exception e){
            System.out.println("---14---");
        }
        System.out.println("---15---");
    }
}

結果:

---1---
---11---
12/-2=-6
---12---
---11---
12/-1=-12
---12---
---11---
---14---
---15---
---2---

查看結果,發現循環還是中斷了,當i=0時,第13行產生異常,之后循環中斷,然后異常才會被for循環之外的異常捕捉到,這種場景也會在實際項目中出現,但不多見,具體場景為,針對循環進行異常捕捉,一旦循環中某一環產生異常,則整個循環終止,處理異常。

改造二:在循環體中加入try...catch塊:

public class ExceptionTests01 {

    public static void main(String[] args) {
        System.out.println("---1---");
        invoke();
        System.out.println("---2---");
    }
    
    public static void invoke(){
        for(int i = -2;i < 3;i++){
            try{
                System.out.println("---11---");
                System.out.println("12/"+i+"="+12/i);
                System.out.println("---12---");
            }catch(Exception e){
                System.out.println("---13---");
            }
            System.out.println("---14---");
        }
        System.out.println("---15---");
    }
}

結果為:

---1---
---11---
12/-2=-6
---12---
---14---
---11---
12/-1=-12
---12---
---14---
---11---
---13---
---14---
---11---
12/1=12
---12---
---14---
---11---
12/2=6
---12---
---14---
---15---
---2---

這種情況比較多見,我們將異常捕捉內置到for循環內部,只針對循環體進行異常捕捉,這樣當某一次循環體執行時產生了異常,也能私下處理好,不會影響整個循環的繼續執行。在循環中還可以結合continue和break關鍵字進行更加復雜的關系控制,來達到特定的業務需求。

這里我來展示一種情況,也是剛剛做過的一個業務場景:

public class ExceptionTests01 {

    public static void main(String[] args) {
        System.out.println("---1---");
        invoke();
        System.out.println("---2---");
    }
    
    public static void invoke(){
        for(int i = -2;i < 3;i++){
            try{
                System.out.println("---11---");
                System.out.println("12/"+i+"="+12/i);
                System.out.println("---12---");
            }catch(Exception e){
                System.out.println("---13---");
                continue;
            }
            System.out.println("---14---");
        }
        System.out.println("---15---");
    }
}

你沒看錯,只是添加了一個continue;控制信息的展示,當發生異常之后,執行catch塊代碼,輸出13后,不再執行輸出14的操作,而是直接開始新的循環。

總結

  1. 異常的捕捉是有方向性和類型針對性的,異常會被距離異常發生點最近的包含或者就是捕捉該類型異常的捕捉點捕捉到。這樣我們在做嵌套異常捕捉和多異常捕捉時,就一定要注意要將小范圍的異常類型放置到靠進try塊的位置,避免大類型劫持異常,導致你設置的異常類型無法生效。
  2. 我們將一段代碼try...catch包裹,就可以將這段代碼從這個方法體中隔離出來,將其影響度降到最低,即使其發生異常,也不會影響到后續代碼的執行。
  3. throw關鍵字的配合使用,我們可以在catch塊中使用,表示將捕捉到的異常再次拋出,這里不做處理,這樣就必須在方法的調用處再次進行捕捉,可以持續拋出,但是直到最終的方法時,必須要進行處理(對應明確拋出的異常一定要進行捕捉處理,不論你拋幾次)。

三、項目中的異常

異常的轉換

項目中我們都會自定義異常,這些異常一般帶有較為明確的目的,甚至我們可能會在項目中的每一層級定義不同的異常,這時候就會涉及到異常的轉換,其實轉換很簡單,只要將異常進行捕捉,在catch塊中將一行捕捉住,并重新拋出(throw)一個新的異常,將之前的異常信息e作為新異常的參數。

try{
    int i = 1/0;   
}catch(Exception e){
    throw new RuntimeException(e);
}

如上面的例子中,第2行會拋出一個異常,該異常將會被catch塊捕捉到,然后內部消化,重新拋出一個RuntimeException,并將原來的異常信息作為新異常的異常信息(即保留原異常信息)。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,460評論 6 538
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,067評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,467評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,468評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,184評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,582評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,616評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,794評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,343評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,096評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,291評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,863評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,513評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,941評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,190評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,026評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,253評論 2 375