原創作品,可以轉載,但是請標注出處地址:http://www.cnblogs.com/V1haoge/p/7191280.html
1、概述
Java代碼中的異常處理是非常重要的一環,從代碼中可以看到,它的使用已經和業務邏輯緊密的結合在一起,部分業務邏輯還是依靠異常來完成的,更多的時候進行異常處理可以完善邏輯,避免可能的出錯,規避小錯誤引發的大停頓。
在一般的項目之中,都會自定義運行時異常,用以適應項目的需要,這種異常可被捕捉,也可不被捕捉,它們不會導致整個系統掛掉,但是很多情況下,不捕捉處理就會導致業務出錯。
在這里我們模擬幾種情況,點明異常捕捉的使用時機。
2、情況分析
先來看沒有任何處理的代碼
1 public class ExceptionTests01 { 2 3 public static void main(String[] args) { 4 System.out.println("---1---"); 5 invoke(); 6 System.out.println("---2---"); 7 8 } 9 10 public static void invoke(){ 11 System.out.println("---11---"); 12 int i = 1/0; 13 System.out.println("---12---"); 14 } 15 }
其執行結果如下:
---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方法內部加上異常捕捉機制,代碼如下:
1 public class ExceptionTests01 { 2 3 public static void main(String[] args) { 4 System.out.println("---1---"); 5 invoke(); 6 System.out.println("---2---"); 7 8 } 9 10 public static void invoke(){ 11 try{ 12 System.out.println("---11---"); 13 int i = 1/0; 14 }catch(Exception e){ 15 System.out.println("---12---"); 16 } 17 System.out.println("---13---"); 18 } 19 }
執行結果:
---1--- ---11--- ---12--- ---13--- ---2---
結果解析:我們在invoke方法的執行代碼外圍添加異常捕捉代碼,捕捉Exception異常,這是所有異常的基類,當然也包含這里的算法異常,那么這個捕捉機制就會將1/0產生的異常捕捉到,捕捉到這個異常之后,就會跳轉到catch語句塊中執行針對這個異常的處理語句,執行完成后,會繼續執行try...catch語句塊之后的代碼,這樣的好處顯而易見,一處的小錯誤並不會阻擋整個代碼的持續執行,當然如果是嚴重問題,我們確實需要暫停執行的,就不能使用這種情況,使用之前的代碼就行,所以異常處理機制的執行時機完全是由項目的業務情況而定的,是非常靈活的,不是固定的死板的。我們要根據實際的業務場景來合理的使用才是正理。
改造二:我們在main方法中也添加異常捕捉
1 public class ExceptionTests01 { 2 3 public static void main(String[] args) { 4 try{ 5 System.out.println("---1---"); 6 invoke(); 7 }catch(Exception e){ 8 System.out.println("---2---"); 9 } 10 System.out.println("---3---"); 11 } 12 13 public static void invoke(){ 14 try{ 15 System.out.println("---11---"); 16 int i = 1/0; 17 }catch(Exception e){ 18 System.out.println("---12---"); 19 } 20 System.out.println("---13---"); 21 } 22 }
其執行結果如下:
---1--- ---11--- ---12--- ---13--- ---3---
結果幾乎與之前的完全一致,不同之處在於2沒有輸出,一是我改變了2的輸出位置,並新增了3的輸出,現在3相當於之前2的位置,2沒有輸出的原因是因為任何一個異常只能被捕捉一次,一旦被捕捉處理,那么之后就不會再次被捕捉,即使我在main方法中將異常類型改成算法異常,也不會捕捉到,異常只會被距離它最近的包含該異常的異常捕捉到,這里的兩個異常捕捉其實就是一個嵌套的異常捕捉,而且二者捕捉的異常還是一致的,一般情況我們是不會這么使用的,因為毫無意義。但不是說它就完全不會出現,可能invoke中的代碼較長,會有多處異常情況出現,我們可以在main方法中統一捕捉,而invoke中的異常捕捉只針對單一異常,表示這個異常的出現不會影響invoke方法后面的代碼執行,沒有異常捕捉的代碼一旦出現異常就會中斷其后方所有代碼的執行(同一代碼塊內),這個異常會被main方法中的異常捕捉機制捕捉到並執行處理,這樣main方法中調用invoke之后的代碼仍然可以執行,不會被調用發生異常而中斷。
但是如果我們再將invoke方法中的異常捕捉改變如下:
1 public class ExceptionTests01 { 2 3 public static void main(String[] args) { 4 try{ 5 System.out.println("---1---"); 6 invoke(); 7 }catch(Exception e){ 8 System.out.println("---2---"); 9 } 10 System.out.println("---3---"); 11 } 12 13 public static void invoke(){ 14 try{ 15 System.out.println("---11---"); 16 int i = 1/0; 17 }catch(NullPointerException e){ 18 System.out.println("---12---"); 19 } 20 System.out.println("---13---"); 21 } 22 }
執行結果發生了變化:
---1--- ---11--- ---2--- ---3---
為什么呢?正是因為我們更改了invoke方法中捕捉的異常類型,之前是異常基類型Exception,現在改成具體的空指針異常,那么這個異常捕捉就只能捕捉空指針異常,它對此處發生的算法異常就會視而不見(由於異常類型的不對口,那么這個異常捕捉相當於沒有添加,可以想象成沒有異常捕捉的情況),這樣就導致invoke方法中在1/0發生異常之后的所有代碼全部不會執行,而我們在main方法中新增的異常捕獲卻能捕獲到這種算法異常,所以12和13都不會輸出,而是在異常發生后直接就跳轉到main方法中進行異常捕捉,執行catch語句塊處理語句輸出2,然后是3。
在看一個特殊的情況:
1 public class ExceptionTests01 { 2 3 public static void main(String[] args) { 4 System.out.println("---1---"); 5 invoke(); 6 System.out.println("---2---"); 7 } 8 9 public static void invoke(){ 10 for(int i = -2;i < 3;i++){ 11 System.out.println("---11---"); 12 System.out.println("12/"+i+"="+12/i); 13 System.out.println("---12---"); 14 } 15 System.out.println("---13---"); 16 } 17 }
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:
1 public class ExceptionTests01 { 2 3 public static void main(String[] args) { 4 System.out.println("---1---"); 5 invoke(); 6 System.out.println("---2---"); 7 } 8 9 public static void invoke(){ 10 try{ 11 for(int i = -2;i < 3;i++){ 12 System.out.println("---11---"); 13 System.out.println("12/"+i+"="+12/i); 14 System.out.println("---12---"); 15 } 16 System.out.println("---13---"); 17 }catch(Exception e){ 18 System.out.println("---14---"); 19 } 20 System.out.println("---15---"); 21 } 22 }
結果:
---1--- ---11--- 12/-2=-6 ---12--- ---11--- 12/-1=-12 ---12--- ---11--- ---14--- ---15--- ---2---
查看結果,發現循環還是中斷了,當i=0時,第13行產生異常,之后循環中斷,然后異常才會被for循環之外的異常捕捉到,這種場景也會在實際項目中出現,但不多見,具體場景為,針對循環進行異常捕捉,一旦循環中某一環產生異常,則整個循環終止,處理異常。
改造二:在循環體中加入try...catch塊
1 public class ExceptionTests01 { 2 3 public static void main(String[] args) { 4 System.out.println("---1---"); 5 invoke(); 6 System.out.println("---2---"); 7 } 8 9 public static void invoke(){ 10 for(int i = -2;i < 3;i++){ 11 try{ 12 System.out.println("---11---"); 13 System.out.println("12/"+i+"="+12/i); 14 System.out.println("---12---"); 15 }catch(Exception e){ 16 System.out.println("---13---"); 17 } 18 System.out.println("---14---"); 19 } 20 System.out.println("---15---"); 21 } 22 }
執行結果:
---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關鍵字進行更加復雜的關系控制,來達到特定的業務需求。
這里我來展示一種情況,也是剛剛做過的一個業務場景:
1 public class ExceptionTests01 { 2 3 public static void main(String[] args) { 4 System.out.println("---1---"); 5 invoke(); 6 System.out.println("---2---"); 7 } 8 9 public static void invoke(){ 10 for(int i = -2;i < 3;i++){ 11 try{ 12 System.out.println("---11---"); 13 System.out.println("12/"+i+"="+12/i); 14 System.out.println("---12---"); 15 }catch(Exception e){ 16 System.out.println("---13---"); 17 continue; 18 } 19 System.out.println("---14---"); 20 } 21 System.out.println("---15---"); 22 } 23 }
你沒看錯,只是添加了一個continue;控制信息的展示,當發生異常之后,執行catch塊代碼,輸出13后,不再執行輸出14的操作,而是直接開始新的循環。
有必要做個總結:
1-異常的捕捉是有方向性和類型針對性的,異常會被距離異常發生點最近的包含或者就是捕捉該類型異常的捕捉點捕捉到。這樣我們在做嵌套異常捕捉和多異常捕捉時,就一定要注意要將小范圍的異常類型放置到靠進try塊的位置,避免大類型劫持異常,導致你設置的異常類型無法生效。
2-我們將一段代碼try...catch包裹,就可以將這段代碼從這個方法體中隔離出來,將其影響度降到最低,即使其發生異常,也不會影響到后續代碼的執行。
3-throw關鍵字的配合使用,我們可以在catch塊中使用,表示將捕捉到的異常再次拋出,這里不做處理,這樣就必須在方法的調用處再次進行捕捉,可以持續拋出,但是直到最終的方法時,必須要進行處理(對應明確拋出的異常一定要進行捕捉處理,不論你拋幾次)。
3、項目的異常處理
異常的轉換
項目中我們都會自定義異常,這些異常一般帶有較為明確的目的,甚至我們可能會在項目中的每一層級定義不同的異常,這時候就會涉及到異常的轉換,其實轉換很簡單,只要將異常進行捕捉,在catch塊中將一行捕捉住,並重新拋出(throw)一個新的異常,將之前的異常信息e作為新異常的參數。
1 try{ 2 int i = 1/0; 3 }catch(Exception e){ 4 throw new RuntimeException(e); 5 }
如上面的例子中,第2行會拋出一個異常,該異常將會被catch塊捕捉到,然后內部消化,重新拋出一個RuntimeException,並將原來的異常信息作為新異常的異常信息(即保留原異常信息)。