在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異常:
Exception中出現的異常:
RunTimeException子類:
非RunTimeException子類:
在 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的執行順序在咱們的平常編碼中可能不會用到,可是可能會出如今你筆試中,因此咱們仍是須要了解一下。
該部份內容轉載自: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是在聲明方法的後面使用,表示此方法不處理異常,而交給方法調用者進行處理,而後一直將異常向上一級拋給調用者,而調用者能夠選擇捕獲或者拋出,若是全部方法(包括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是容許咱們在程序中手動拋出異常的,那麼這就操蛋了,咱們都恨不得不出現任何異常,這咋還得本身來拋出異常呢!這是由於有些地方確實須要拋出異常,咱們簡單舉例來看:
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 }
運行結果: