夯實Java基礎(十二)——異常處理

一、異常處理概述

在Java程序執行過程當中, 老是會發生不被指望的事件, 阻止程序按照程序員預期正常運行, 這就是Java程序出現的異常。html

異常處理是基於面向對象的一種運行錯誤處理機制,經過對異常問題的封裝,實現對用戶的非法操做、參數設置異常,硬件系統異常,網絡狀態改變異常等運行態中可能出現的異常信息的處理機制。java

若是某個方法不能按照正常的途徑完成任務,就能夠經過另外一種路徑退出方法。在這種狀況下會拋出一個封裝了錯誤信息的對象。此時,這個方法會馬上退出同時不返回任何值。另外,調用這個方法的其餘代碼也沒法繼續執行,異常處理機制會將代碼執行交給異常處理器來處理,這就是Java異常的處理。Java爲咱們提供了很是完美的異常處理機制,咱們看下面的圖。程序員

異常結構圖(圖片來自百度):數據庫

從圖的結構咱們能夠知道,全部的異常都是繼承自Throwable,有兩個子類Error和Exception,它們分別表示錯誤和異常。數組

咱們來看看Error與Exception 的具體描述:安全

Error是程序沒法處理的錯誤。好比VirtualMachineError、OutOfMemoryError、ThreadDeath等。當這些異常發生時, Java虛擬機通常會選擇線程終止。 網絡

Exception是程序自己能夠處理的異常。這種異常它們又分爲兩大類RunTimeException(運行時異常)和非RunTimeException(非運行時異常),在程序中咱們應當儘量去處理這些異常。 函數

CheckException是受檢查異常,它發生在編譯階段。全部CheckException都是須要在代碼中處理的,因此這對咱們在編碼時是很是有幫助的。它們的發生是能夠預測的,是能夠合理的處理。要麼使用try-catch-finally語句進行捕獲,要麼用throws子句拋出,不然編譯就會報錯。在Exception中,除了RuntimeException及其子類之外,其餘都是都是CheckedException。學習

UncheckedException是不受檢查異常,它發生只有在運行期間。全部是沒法預先捕捉處理的,主要是因爲程序的邏輯錯誤所引發的。Error也是UncheckedException,也是沒法預先處理的,它們都難以排查。因此在咱們的程序中應該從邏輯角度出發,儘量避免這類異常的發生。 編碼

既然有些時候錯誤和異常不可避免,那麼咱們能夠在程序設計中認真的考慮,設計出更加高質量的代碼,這樣即便產生了異常,也能儘可能保證程序朝着有利方向發展。

二、常見異常

在Java中異常的種類很是的多,因此咱們就列出比較常見的異常。

Error異常:

  • OutOfMemoryError:內存不足錯誤。當可用內存不足以讓Java虛擬機分配給一個對象時拋出該錯誤。
  • StackOverflowError:堆棧溢出錯誤。當一個應用遞歸調用的層次太深而致使堆棧溢出時拋出該錯誤。
  • VirtualMachineError:虛擬機錯誤。用於指示虛擬機被破壞或者繼續執行操做所需的資源不足的狀況。
  • ThreadDeath:線程結束。當調用Thread類的stop方法時拋出該錯誤,用於指示線程結束。
  • UnknownError:未知錯誤。用於指示Java虛擬機發生了未知嚴重錯誤的狀況。

Exception中出現的異常:

RunTimeException子類:

  • NullPointerException:空指針異常。
  • ArrayIndexOutOfBoundsException:數組索引越界異常。
  • ClassNotFoundException:找不到類異常。
  • ClassCastException:類型轉換異常類。
  • NumberFormatException:字符串轉換爲數字拋出的異常。
  • ArithmeticException:算術條件異常。如:整數除零等。
  • NegativeArraySizeException:數組長度爲負異常。
  • ArrayStoreException:數組中包含不兼容的值拋出的異常。
  • SecurityException:安全性異常。
  • IllegalArgumentException:非法參數異常。
  • NoSuchMethodException:方法未找到異常。

非RunTimeException子類:

  • IOException:輸入輸出流異常。
  • EOFException:文件已結束異常。
  • SQLException:操做數據庫異常。
  • 自定義Exception:用戶本身定義的異常。

三、異常處理try-catch-finally

在 Java 應用程序中,異常處理機制爲:拋出異常,捕獲異常。接下來咱們就來學習怎麼用try-catch-finally語句來捕獲異常。

先看一下try-catch-finally語句的格式:

try{
    //可能生成異常的代碼    
}catch(Exception e){
    //處理異常的代碼
}catch(Exception e){
    //處理異常的代碼
} finally {
    //必定會執行的代碼          
}

而後咱們用一個整數除以零爲例,來使用try-catch-finally捕獲異常:

 1 @Test
 2 public void test3(){
 3     int i=10;
 4     try {
 5         int j=i/0;//會出現異常的地方
 6         System.out.println(j);
 7     } catch (Exception e) {
 8         e.printStackTrace();
 9         System.out.println("代碼出現了異常...");
10     } finally {
11         System.out.println("必定會執行...");
12     }
13 }

運行結果:

咱們知道任何數是不能夠除零的,因此這個地方必定會拋出異常,咱們用try-catch給它包起來。從結果能夠看出來,當程序遇到異常時會終止程序的運行(即後面的代碼不在執行),控制權交由異常處理機制處理。由catch捕獲異常後,再執行catch中的語句。而後再執行finally中必定會執行的語句(finally語句塊通常都是去釋放佔用的資源)。

從上面的例子咱們會不會認爲try-catch-finally捕獲異常很是的簡單,然而它們真的很是簡單嗎?咱們再來看下面這個例子。

 1 public class TryTest {
 2     public static void main(String[] args){
 3         System.out.println(test());
 4     }
 5 
 6     private static int test(){
 7         int num = 10;
 8         try{
 9             System.out.println("try");
10             return num += 80;
11         }catch(Exception e){
12             System.out.println("error");
13         }finally{
14             if (num > 20){
15                 System.out.println("num>20 : " + num);
16             }
17             System.out.println("finally");
18         }
19         return num;
20     }
21 }

運行結果:

看到這樣的結果必定會很是的懵逼,你能夠本身先思考一下,這就是下面要講的try-catch-finally中有無return的執行順序。

四、try-catch-finally的執行順序

對於try-catch-finally的執行順序在咱們的平常編碼中可能不會用到,可是可能會出如今你筆試中,因此咱們仍是須要了解一下。

該部份內容轉載自:https://blog.csdn.net/ns_code/article/details/17485221

在這裏看到了try catch finally塊中含有return語句時程序執行的幾種狀況,但其實總結的並不全,並且分析的比較含糊。但有一點是能夠確定的,finally塊中的內容會先於try中的return語句執行,若是finall語句塊中也有return語句的話,那麼直接從finally中返回了,這也是不建議在finally中return的緣由。下面來看這幾種狀況。

狀況一(try中有return,finally中沒有return)

 1 public class TryTest{
 2     public static void main(String[] args){
 3         System.out.println(test());
 4     }
 5  
 6     private static int test(){
 7         int num = 10;
 8         try{
 9             System.out.println("try");
10             return num += 80;
11         }catch(Exception e){
12             System.out.println("error");
13         }finally{
14             if (num > 20){
15                 System.out.println("num>20 : " + num);
16             }
17             System.out.println("finally");
18         }
19         return num;
20     }
21 }

輸出結果以下:

分析:顯然「return num += 80」被拆分紅了「num = num+80」和「return num」兩個語句,線執行try中的「num = num+80」語句,將其保存起來,在try中的」return num「執行前,先將finally中的語句執行完,然後再將90返回。

狀況二(try和finally中均有return)

 1 public class TryTest{
 2     public static void main(String[] args){
 3         System.out.println(test());
 4     }
 5  
 6     private static int test(){
 7         int num = 10;
 8         try{
 9             System.out.println("try");
10             return num += 80;
11         }catch(Exception e){
12             System.out.println("error");
13         }finally{
14             if (num > 20){
15                 System.out.println("num>20 : " + num);
16             }
17             System.out.println("finally");
18             num = 100;
19             return num;
20         }
21     }
22 }

輸出結果以下:

分析:try中的return語句一樣被拆分了,finally中的return語句先於try中的return語句執行,於是try中的return被」覆蓋「掉了,再也不執行。

狀況三(finally中改變返回值num):

 1 public class TryTest{
 2     public static void main(String[] args){
 3         System.out.println(test());
 4     }
 5  
 6     private static int test(){
 7         int num = 10;
 8         try{
 9             System.out.println("try");
10             return num;
11         }catch(Exception e){
12             System.out.println("error");
13         }finally{
14             if (num > 20){
15                 System.out.println("num>20 : " + num);
16             }
17             System.out.println("finally");
18             num = 100;
19         }
20         return num;
21     }
22 }

輸出結果以下:

分析:雖然在finally中改變了返回值num,但由於finally中沒有return該num的值,所以在執行完finally中的語句後,test()函數會獲得try中返回的num的值,而try中的num的值依然是程序進入finally代碼塊前保留下來的值,所以獲得的返回值爲10。

可是咱們來看下面的狀況(將num的值包裝在Num類中):

 1 public class TryTest{
 2     public static void main(String[] args){
 3         System.out.println(test().num);
 4     }
 5  
 6     private static Num test(){
 7         Num number = new Num();
 8         try{
 9             System.out.println("try");
10             return number;
11         }catch(Exception e){
12             System.out.println("error");
13         }finally{
14             if (number.num > 20){
15                 System.out.println("number.num>20 : " + number.num);
16             }
17             System.out.println("finally");
18             number.num = 100;
19         }
20         return number;
21     }
22 }
23  
24 class Num{
25     public int num = 10;
26 }

輸出結果以下:

從結果中能夠看出,一樣是在finally中改變了返回值num的值,在狀況三中,並無被try中的return返回(test()方法獲得的不是100),但在這裏卻被try中的return語句返回了。

對以上狀況的分析,須要深刻JVM虛擬機中程序執行exection_table中的字節碼指令時操做棧的的操做狀況,能夠參考http://www.2cto.com/kf/201010/76754.html這篇文章,也能夠參考《深刻Java虛擬機:JVM高級特性與最佳實踐》第6章中對屬性表集合的講解部分。

對於含有return語句的狀況,這裏咱們能夠簡單地總結以下:

try語句在返回前,將其餘全部的操做執行完,保留好要返回的值,然後轉入執行finally中的語句,然後分爲如下三種狀況:

狀況一:若是finally中有return語句,則會將try中的return語句」覆蓋「掉,直接執行finally中的return語句,獲得返回值,這樣便沒法獲得try以前保留好的返回值。

狀況二:若是finally中沒有return語句,也沒有改變要返回值,則執行完finally中的語句後,會接着執行try中的return語句,返回以前保留的值。

狀況三:若是finally中沒有return語句,可是改變了要返回的值,這裏有點相似與引用傳遞和值傳遞的區別,分如下兩種狀況,:

        1)若是return的數據是基本數據類型或文本字符串,則在finally中對該基本數據的改變不起做用,try中的return語句依然會返回進入finally塊以前保留的值。

        2)若是return的數據是引用數據類型,而在finally中對該引用數據類型的屬性值的改變起做用,try中的return語句返回的就是在finally中改變後的該屬性的值。

五、異常處理throws+異常類型(異常鏈)

throws是在聲明方法的後面使用,表示此方法不處理異常,而交給方法調用者進行處理,而後一直將異常向上一級拋給調用者,而調用者能夠選擇捕獲或者拋出,若是全部方法(包括main)都選擇拋出。那麼最終將會拋給JVM。由JVM來打印出信息(異常鏈)。 

 1 public class ThrowsTest {
 2     public static void method1() throws IOException {
 3         File file=new File("hello.txt");
 4         FileInputStream fis=new FileInputStream(file);
 5         int data=fis.read();
 6         while (data != -1) {
 7             System.out.println(data);
 8             data=fis.read();
 9         }
10         fis.close();
11     }
12 
13     public static void method2() throws IOException {
14         method1();
15     }
16 
17     public static void main(String[] args) throws IOException {
18         method2();
19     }
20 }
21 //結果;java.io.FileNotFoundException: hello.txt (系統找不到指定的文件。)

上面代碼的異常信息最終由JVM打印,一樣咱們也能夠對異常進行捕獲。

1     public static void main(String[] args){
2         try {
3             method2();
4         } catch (IOException e) {
5             e.printStackTrace();
6         }
7     }

經過使用throws+異常類型(異常鏈),咱們能夠提升代碼的可理解性、系統的可維護性。

六、手動拋出異常throw

使用throw是容許咱們在程序中手動拋出異常的,那麼這就操蛋了,咱們都恨不得不出現任何異常,這咋還得本身來拋出異常呢!這是由於有些地方確實須要拋出異常,咱們簡單舉例來看:

 1 public class ThrowTest {
 2 
 3     public void show(int age) throws Exception {
 4         if (age>0&&age<256){
 5             System.out.println(age);
 6         }else{
 7             //System.out.println("輸入年齡有誤!");
 8             throw new Exception("輸入年齡有誤!");
 9         }
10         System.out.println(age);
11     }
12     public static void main(String[] args) {
13         ThrowTest test=new ThrowTest();
14         try {
15             test.show(500);
16         } catch (Exception e) {
17             e.printStackTrace();
18         }
19     }
20 }

上面的例子中若是咱們使用System.out.println("輸入年齡有誤!");輸出信息,然而它僅僅是一個輸出的功能,並不會終止後面代碼的執行,因此這時咱們就能夠選擇手動拋出異常來終止代碼的運行。

咱們發現throws和throw這兩個很是的類似,來看看它們的區別是什麼:

throws:用來聲明一個方法可能產生的全部異常,該方法不作任何處理,而是一直將異常往上一級傳遞,由調用者繼續拋出或捕獲。它用在方法聲明的後面,跟的是異常類名,能夠跟多個異常類名,用逗號隔開,它表示的是向上拋出異常,由該方法的調用者來處理

throw:用來拋出一個異常,它用在方法體內部,拋出的異常的對象名稱。它表示拋出異常,由方法體內的語句處理。

七、自定義異常

前面講了異常的拋出和處理,而那些異常都是JDK早已經定義好的。那麼咱們本身怎麼定義異常呢?Java是可讓用戶本身定義異常的,可是必定要注意的是:在咱們自定義異常時,必定要是Throwable的子類,若是是檢查異常就要繼承自Exception,若是是運行異常就要繼承自RuntimeException。咱們舉例來看下。

 1 //自定義異常,繼承Exception
 2 public class MyException extends Exception {
 3     //定義無參構造方法
 4     public MyException() {
 5     }
 6     //定義有參構造方法
 7     public MyException(String message) {
 8         super(message);
 9     }
10 
11 }
12 
13 class Test{
14     public void show(int i)throws Exception{
15         if (i>=0){
16             System.out.println(i);
17         }else{
18             throw new MyException("不能爲負數...");
19         }
20     }
21 
22     public static void main(String[] args) {
23         Test test=new Test();
24         try {
25             test.show(-5);
26         } catch (Exception e) {
27             e.printStackTrace();
28         }
29     }
30 }

 運行結果:

相關文章
相關標籤/搜索