Java基礎-異常(Exception)處理
作者:尹正傑
版權聲明:原創作品,謝絕轉載!否則將追究法律責任。
一.異常的概述
什么是異常?Java代碼在運行時期發生的問題就是異常。在Java中,把異常信息封裝成了一個類。當出現了問題時,就會創建異常類對象,並拋出異常相關信息(如異常信息出現的位置,原因等)。
二.異常的繼承體系
在Java中使用Exception類來描述異常。Exception類及其子類是Throwable的一種形式,它指出了合理應用程序想要捕獲的異常條件。查看Java的API文檔我們可以發現Exception有繼承關系,它的父類是Throwable。Throwable是Java語言中所有錯誤或異常的超類。另外,在異常Exception類中,有一個子類要特殊說明一下,RuntimeException子類,RuntimeException及其它的子類只能在Java程序運行過程中出現。
我們再來觀察Throwable類,能夠發現與異常Exception平級的有一個Error,它是Throwable的子類,它用來表示Java程序中可能會產生的嚴重錯誤。解決辦法只有一個,修改代碼避免Error錯誤的產生。下面是一個Error異常的案例:

綜上所述,異常繼承體系可以大致做以下簡要分類,此處並未列出全部的異常,請以Java的API文檔為標准,下圖只是為了方便記憶:

三.異常對象的產生原因和處理方式

我們看見上面的代碼是有異常拋出的,那么異常究竟是怎么拋出的呢?其實他大致分為以下幾個步驟:
1>.JVM檢測到異常
通過上面的學習,我們知道異常的祖宗其實就是Throwable類,在這個類下面很多個子類已經提前定義好了。在代碼運行的時候,JVM是完全有能力檢測到出現的異常信息(比如:ArrayIndexOutOfBoundsException)。
2>.JVM創建異常對象
當JVM虛擬機檢測到異常后,首先會創建異常對象(比如:new java.lang.ArrayIndexOutOfBoundsException: 5)。
3>.將異常拋給方的調用者
當創建好異常后,首先JVM虛擬機會檢測程序手否對這種類型的異常有相應的處理方式,如果有,則按照業務邏輯執行,如果沒有,就會將異常的對象進行拋出,最終會拋給方法的調用者。
4>.調用者繼續拋出異常
從上面的代碼中可以看到,getAway方法的調用者是main函數,因此會把異常拋給main方法,JVM虛擬機又會在main方法查找是否有異常處理的方式,如果沒有對數組越界異常進行處理就將異常對象(java.lang.ArrayIndexOutOfBoundsException: 5)繼續向上拋出,也就是main方法的調用者。而main方法運行在棧內存中,實際上是拋給了JVM虛擬機啦。
5>.JVM虛擬機收到異常后的事情
其實JVM虛擬機(最終異常的處理者)收到異常后,做了兩件事情:
a>.將異常信息的內容輸出,讓程序員進行排錯;
b>.殺死拋出異常的進程(也就是結束main方法);
四.方法內部拋出對象關鍵字(throw)
在編寫程序時,我們必須要考慮程序出現問題的情況。比如,在定義方法時,方法需要接受參數。那么,當調用方法使用接受到的參數時,首選需要先對參數數據進行合法判斷,數據若不合法,就應該告訴調用者,傳遞合法的數據進來。這時需要使用拋出異常的方式來告訴調用者。在Java中,提供了一個throw關鍵字,它用來拋出一個指定的異常對象。那么,拋出一個異常具體如何操作呢?
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 7 public class ExceptionDemo{ 8 public static void main(String[] args){ 9 int[] arr = null; 10 // int[] arr1 = {}; 11 int i = getArray(arr); 12 System.out.println(i); 13 } 14 15 //對數組的最后索引*2,返回 16 public static int getArray(int[] arr){ 17 //對方法參數進行合法性的判斷,進行判斷是不是null 18 if(arr == null){ 19 //通過關鍵字throw拋出異常的形式,告訴調用者。 20 throw new RuntimeException("傳遞的數組不存在"); 21 } 22 23 24 //對數組進行判斷,判斷數組中是否儲存在元素。 25 if(arr.length == 0){ 26 //以拋出異常的形式,告訴調用者,數組中沒有元素 27 throw new RuntimeException("傳遞的是空數組"); 28 }else{ 29 int i = arr[arr.length-1]; 30 return i*2; 31 } 32 } 33 } 34 35 36 /* 37 以上代碼執行結果如下: 38 Exception in thread "main" java.lang.RuntimeException: 傳遞的數組不存在 39 at ExceptionDemo.getArray(ExceptionDemo.java:20) 40 at ExceptionDemo.main(ExceptionDemo.java:11) 41 */ 42 43 44
五.異常方法聲明關鍵字(throws)
throws用於在方法的聲明上,標明此方法可能出現異常的類型,讓調用者自己處理。換句話說,調用了一個拋出異常的方法,調用者就必須處理,若不處理,就會編譯失敗。
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 7 public class ExceptionDemo{ 8 //main方法將異常拋給了JVM虛擬機。 9 public static void main(String[] args) throws Exception{ 10 // int[] arr1 = null; 11 int[] arr2 = {}; 12 int i = getArray(arr2); 13 System.out.println(i); 14 } 15 16 //對數組的最后索引*2,通過throws關鍵字聲明拋出的異常,調用者必須處理,否則編譯失敗! 17 public static int getArray(int[] arr) throws Exception{ 18 //對方法參數進行合法性的判斷,進行判斷是不是null 19 if(arr == null){ 20 //通過關鍵字throw拋出異常的形式,告訴調用者。 21 throw new Exception("傳遞的數組不存在"); 22 } 23 24 25 //對數組進行判斷,判斷數組中是否儲存在元素。 26 if(arr.length == 0){ 27 //以拋出異常的形式,告訴調用者,數組中沒有元素 28 throw new Exception("傳遞的是空數組"); 29 }else{ 30 int i = arr[arr.length-1]; 31 return i*2; 32 } 33 } 34 } 35 36 37 /* 38 以上代碼執行結果如下: 39 Exception in thread "main" java.lang.Exception: 傳遞的是空數組 40 at ExceptionDemo.getArray(ExceptionDemo.java:28) 41 at ExceptionDemo.main(ExceptionDemo.java:12) 42 */ 43 44 45
六.Java中的異常處理方式
1>.異常處理格式
1 try{ 2 被檢測的代碼; 3 可能出現異常的代碼; 4 }catch(異常類名變量){ 5 異常的處理方式; 6 循環,判斷,調用方法,變量 7 }finally{ 8 必須要執行的代碼; 9 }
2>.try...異常處理
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 7 public class ExceptionDemo{ 8 //main方法進行處理異常 9 public static void main(String[] args){ 10 int[] arr = null; 11 try{ 12 int i = getArray(arr); 13 System.out.println(i); 14 }catch(NullPointerException e) { 15 System.out.println(e); 16 } 17 System.out.println("Game Over!"); 18 } 19 20 //創建異常對象並拋出 21 public static int getArray(int[] arr) throws NullPointerException{ 22 //對方法參數進行合法性的判斷,進行判斷是不是null 23 if(arr == null){ 24 //手動拋出異常,拋出空指針異常 25 throw new NullPointerException("傳遞的數組不存在"); 26 } 27 28 //對數組的索引進行判斷 29 if(arr.length < 3){ 30 //手動拋出異常,拋出數組的索引越界異常 31 throw new ArrayIndexOutOfBoundsException("數組沒有下標為3的索引"); 32 } 33 return arr[3]+1; 34 } 35 } 36 37 38 39 40 41 42 /* 43 以上代碼執行結果如下: 44 java.lang.NullPointerException: 傳遞的數組不存在 45 Game Over! 46 47 48 */ 49 50 51

3>.異常多catch處理
平級異常,拋出的異常類之間,沒有繼承關系。上下級關系的異常,越高級的父類越應該寫在下面。
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 7 public class ExceptionDemo{ 8 //main方法進行處理異常 9 public static void main(String[] args){ 10 int[] arr = new int[0]; 11 try{ 12 int i = getArray(arr); 13 System.out.println(i); 14 }catch(NullPointerException e) { 15 System.out.println(e); 16 }catch(ArrayIndexOutOfBoundsException e) { 17 System.out.println(e); 18 }catch(Exception e){ //Exception應該放在最后一個catch,因為它包含上面2種異常。 19 System.out.println(e); 20 } 21 System.out.println("Game Over!"); 22 } 23 24 //可以拋出多個異常 25 public static int getArray(int[] arr) throws NullPointerException,ArrayIndexOutOfBoundsException{ 26 //對方法參數進行合法性的判斷,進行判斷是不是null 27 if(arr == null){ 28 //手動拋出異常,拋出空指針異常 29 throw new NullPointerException("傳遞的數組不存在"); 30 } 31 32 //對數組的索引進行判斷 33 if(arr.length < 3){ 34 //手動拋出異常,拋出數組的索引越界異常 35 throw new ArrayIndexOutOfBoundsException("數組沒有下標為3的索引"); 36 } 37 return arr[3]+1; 38 } 39 } 40 41 42 43 44 /* 45 以上代碼執行結果如下: 46 java.lang.ArrayIndexOutOfBoundsException: 數組沒有下標為3的索引 47 Game Over! 48 */
4>.finnal代碼塊
finally中的代碼塊無論程序是否有異常,程序都必須執行里面的代碼(除非你在來到finally之前就用System.exit(0)退出程序啦!)。它一般用來釋放IO資源,比如關閉文件或者關閉數據庫的鏈接等等。
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 7 public class ExceptionDemo{ 8 //main方法進行處理異常 9 public static void main(String[] args){ 10 int[] arr = new int[0]; 11 try { 12 function(100); 13 }catch(Exception e){ 14 System.out.println(e); 15 }finally { 16 System.out.println("必須要執行的代碼!"); 17 } 18 System.out.println("Game Over!"); 19 } 20 21 //創建異常對象並拋出 22 public static void function(int a) throws Exception{ 23 if(a == 0) { 24 throw new Exception(); 25 }else { 26 System.out.println(a); 27 } 28 } 29 } 30 31 32 33 34 35 36 /* 37 以上代碼執行結果如下: 38 100 39 必須要執行的代碼! 40 Game Over! 41 */
5>.運行時期異常的特點
異常分為編譯異常和運行時異常。
a>.編譯異常
調用了拋出異常的方法(拋出編譯異常需要在方法上用關鍵字throws聲明異常),不處理的話就會編譯不通過。處理方式有兩種,第一種就是用try語句進行處理,或者是用throws語句丟給調用者去處理。
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 7 public class ExceptionDemo{ 8 //main方法進行處理異常 9 public static void main(String[] args){ 10 int[] arr = new int[0]; 11 try { 12 function(100); 13 }catch(Exception e){ 14 System.out.println(e); 15 }finally { 16 System.out.println("必須要執行的代碼!"); 17 } 18 System.out.println("Game Over!"); 19 } 20 21 //拋出編譯異常需要在方法上用關鍵字throws聲明異常 22 public static void function(int a) throws Exception{ 23 //創建編譯異常 24 throw new Exception(); 25 } 26 } 27 28 29 30 31 /* 32 以上代碼執行結果如下: 33 java.lang.Exception 34 必須要執行的代碼! 35 Game Over! 36 37 */
b>.運行時期異常
拋出的異常是RuntimeException類,或者是他的子類。方法內部拋出的異常是運行異常,在方法聲明上,不需要throws語句。不僅如此,運行時異常我們也不需要去用try語句或者throws語句去處理。
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 7 public class ExceptionDemo{ 8 //main方法進行處理異常 9 public static void main(String[] args){ 10 //調用者,不需要處理異常 11 function(100); 12 } 13 //運行時異常可以不用在方法上用關鍵字throws聲明。 14 public static void function(int a){ 15 //創建運行時異常 16 throw new RuntimeException(); 17 } 18 }
c>.運行異常的設計原因
運行異常,在編譯的時候不能察覺出來,如果發生了運行異常,程序人員停止程序修改源代碼。運行異常一旦發生,后面的代碼沒有執行的意義。比如我們看下面一段代碼,估計身為老司機的你一眼就看出問題了,但是在編譯的時候就不死活不報錯,一旦你運行就會崩潰。
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 7 8 public class ExceptionDemo{ 9 public static void main(String[] args){ 10 int[] arr = {1,2,3}; 11 function(arr); 12 } 13 14 public static void function(int[] arr){ 15 /* 此處我們對數組的第六個元素進行操作,但是如果傳入的數組長度不到6, 16 *則繼續向下執行代碼就沒有任何意義,程序員應該修改以下的代碼邏輯性! 17 */ 18 if(arr[5] > 100) { 19 arr[5] = arr[5]/10; 20 }else { 21 arr[5] = arr[5]/3; 22 } 23 } 24 }
d>.運行異常的案例
定義一個方法,計算一個圓形面積。傳遞參數為負數時可以完成計算,但是違反了真實情況,因此我們可以將傳入的數據進行判斷,如果不符合現實情況就讓程序崩潰掉,讓調用者傳入合法的數據信息,我們舉一個簡單的案例(求圓形的面積,你可以補充求周長來練習):
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 7 8 public class ExceptionDemo{ 9 public static void main(String[] args){ 10 //傳入一個負數去求面積,會導致程序崩潰 11 double d = getArea(-1); 12 System.out.printf("圓形的面積是:%f\n",d); 13 } 14 15 //定義方法,計算圓形的面積 16 public static double getArea(double r){ 17 //當傳入的半徑是非正數時,就讓程序崩潰掉! 18 if(r <= 0) { 19 throw new RuntimeException("圓形不存在"); 20 } 21 return r*r*Math.PI; 22 23 } 24 } 25 26 27 28 /* 29 以上代碼執行結果如下: 30 Exception in thread "main" java.lang.RuntimeException: 圓形不存在 31 at ExceptionDemo.getArea(ExceptionDemo.java:19) 32 at ExceptionDemo.main(ExceptionDemo.java:11) 33 34 */
6>.方法重寫時候異常的處理
繼承后,在子類重寫父類方法的時候,異常處理結論:
a>.父類的方法如果拋出異常,子類重寫后可以不拋出異常,也可以不拋出異常,但是,如果子類要拋,拋出的異常不能大於父類的異常(都指的是繼承關系);
b>.父類的方法沒有異常拋出,子類重寫后也不能拋出異常;
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 7 8 public class ExceptionDemo{ 9 public static void main(String[] args){ 10 11 } 12 } 13 14 class Father{ 15 public void function()throws Exception{ 16 17 } 18 } 19 20 class Son extends Father{ 21 //子類可以拋出異常也可以不拋出異常Exception 22 public void function(){ 23 24 } 25 }
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 7 8 public class ExceptionDemo{ 9 public static void main(String[] args){ 10 11 } 12 } 13 14 class Father{ 15 public void function(){ 16 17 } 18 } 19 20 class Son extends Father{ 21 //子類可以拋出異常也可以不拋出異常Exception 22 public void function(){ 23 24 } 25 }
七.自定義異常
1>.Throwable類常用的方法
a>.String getMessage() :對異常信息的詳細描述。
b>.String toString() :對異常信息的簡短描述。
c>.void printStackTrace() :將異常信息追蹤到標准的錯誤流,也是JVM虛擬機默認調用的方式,因為它的異常信息最詳細。
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 7 8 public class ExceptionDemo{ 9 public static void main(String[] args){ 10 try { 11 function(); 12 }catch(Exception e) { 13 System.out.println(e.getMessage()); 14 System.out.println(e.toString()); 15 e.printStackTrace(); //JVM默認調用的就是這個方法,異常信息最全。 16 } 17 18 } 19 20 21 public static void function() throws Exception{ 22 throw new Exception("異常啦!"); 23 } 24 25 } 26 27 28 29 /* 30 以上代碼執行結果如下: 31 異常啦! 32 java.lang.Exception: 異常啦! 33 java.lang.Exception: 異常啦! 34 at ExceptionDemo.function(ExceptionDemo.java:22) 35 at ExceptionDemo.main(ExceptionDemo.java:11) 36 */
2>.自定義異常
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 7 8 //自定義異常類 9 class MyException extends RuntimeException{ 10 11 MyException(String message){ 12 super(message); 13 } 14 } 15 16 public class ExceptionDemo{ 17 public static void main(String[] args){ 18 try{ 19 test(); 20 }catch(MyException e){ 21 System.out.println(e.getMessage()); 22 //.... 23 } 24 } 25 26 public static void test() throws MyException { 27 throw new MyException("發生自定義異常!"); 28 } 29 } 30 31 32 33 /* 34 以上代碼執行結果如下: 35 發生自定義異常! 36 */
