JAVA基礎——異常詳解

JAVA異常與異常處理詳解

1、異常簡介

什麼是異常?html

異常就是有異於常態,和正常狀況不同,有錯誤出錯。在java中,阻止當前方法或做用域的狀況,稱之爲異常。前端

java中異常的體系是怎麼樣的呢?java

1.Java中的全部不正常類都繼承於Throwable類。Throwable主要包括兩個大類,一個是Error類,另外一個是Exception類;mysql

    

 2.其中Error類中包括虛擬機錯誤線程死鎖,一旦Error出現了,程序就完全的掛了,被稱爲程序終結者程序員

   

3.Exception類,也就是一般所說的「異常」。主要指編碼、環境、用戶操做輸入出現問題,Exception主要包括兩大類,非檢查異常(RuntimeException)和檢查異常(其餘的一些異常)sql

    

4.RuntimeException異常主要包括如下四種異常(其實還有不少其餘異常,這裏不一一列出):空指針異常、數組下標越界異常、類型轉換異常、算術異常。RuntimeException異常會由java虛擬機自動拋出並自動捕獲(就算咱們沒寫異常捕獲語句運行時也會拋出錯誤!!),此類異常的出現絕大數狀況是代碼自己有問題應該從邏輯上去解決並改進代碼。數據庫

  

5.檢查異常,引發該異常的緣由多種多樣,好比說文件不存在、或者是鏈接錯誤等等。跟它的「兄弟」RuntimeException運行異常不一樣,該異常咱們必須手動在代碼裏添加捕獲語句來處理該異常,這也是咱們學習java異常語句中主要處理的異常對象。編程

  

2、try-catch-finally語句

(1)try塊:負責捕獲異常,一旦try中發現異常,程序的控制權將被移交給catch塊中的異常處理程序。數組

  【try語句塊不能夠獨立存在,必須與 catch 或者 finally 塊同存】安全

(2)catch塊:如何處理?好比發出警告:提示、檢查配置、網絡鏈接,記錄錯誤等。執行完catch塊以後程序跳出catch塊,繼續執行後面的代碼。

    【編寫catch塊的注意事項:多個catch塊處理的異常類,要按照先catch子類後catch父類的處理方式,由於會【就近處理】異常(由上自下)。

(3)finally:最終執行的代碼,用於關閉和釋放資源。

=======================================================================

語法格式以下:

try{
//一些會拋出的異常
}catch(Exception e){
//第一個catch
//處理該異常的代碼塊
}catch(Exception e){
//第二個catch,能夠有多個catch
//處理該異常的代碼塊
}finally{
//最終要執行的代碼
} 

當異常出現時,程序將終止執行,交由異常處理程序(拋出提醒或記錄日誌等),異常代碼塊外代碼正常執行。 try會拋出很多種類型的異常,由多個catch塊捕獲多鍾錯誤

多重異常處理代碼塊順序問題先子類再父類(順序不對編譯器會提醒錯誤),finally語句塊處理最終將要執行的代碼。

=======================================================================

接下來,咱們用實例來鞏固try-catch語句吧~

先看例子:

 1 package com.hysum.test;
 2 
 3 public class TryCatchTest {
 4     /**
 5      * divider:除數
 6      * result:結果
 7      * try-catch捕獲while循環
 8      * 每次循環,divider減一,result=result+100/divider
 9      * 若是:捕獲異常,打印輸出「異常拋出了」,返回-1
10      * 不然:返回result
11      * @return
12      */
13     public int test1(){
14         int divider=10;
15         int result=100;
16         try{
17             while(divider>-1){
18                 divider--;
19                 result=result+100/divider;
20             }
21             return result;
22         }catch(Exception e){
23             e.printStackTrace();
24             System.out.println("異常拋出了!!");
25             return -1;
26         }
27     }
28     public static void main(String[] args) {
29         // TODO Auto-generated method stub
30         TryCatchTest t1=new TryCatchTest();
31         System.out.println("test1方法執行完畢!result的值爲:"+t1.test1());
32     }
33     
34 }

運行結果:

結果分析:結果中的紅色字拋出的異常信息是由e.printStackTrace()來輸出的,它說明了這裏咱們拋出的異常類型是算數異常,後面還跟着緣由:by zero(由0形成的算數異常),下面兩行at代表了形成此異常的代碼具體位置。

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

在上面例子中再加上一個test2()方法來測試finally語句的執行情況:

 1     /**
 2      * divider:除數
 3      * result:結果
 4      * try-catch捕獲while循環
 5      * 每次循環,divider減一,result=result+100/divider
 6      * 若是:捕獲異常,打印輸出「異常拋出了」,返回result=999
 7      * 不然:返回result
 8      * finally:打印輸出「這是finally,哈哈哈!!」同時打印輸出result
 9      * @return
10      */
11     public int test2(){
12         int divider=10;
13         int result=100;
14         try{
15             while(divider>-1){
16                 divider--;
17                 result=result+100/divider;
18             }
19             return result;
20         }catch(Exception e){
21             e.printStackTrace();
22             System.out.println("異常拋出了!!");
23             return result=999;
24         }finally{
25             System.out.println("這是finally,哈哈哈!!");
26             System.out.println("result的值爲:"+result);
27         }
28         
29     }
30     
31     
32     
33     public static void main(String[] args) {
34         // TODO Auto-generated method stub
35         TryCatchTest t1=new TryCatchTest();
36         //System.out.println("test1方法執行完畢!result的值爲:"+t1.test1());
37         t1.test2();
38         System.out.println("test2方法執行完畢!");
39     }

運行結果:

結果分析:咱們能夠從結果看出,finally語句塊是在try塊和catch塊語句執行以後最後執行的。finally是在return後面的表達式運算後執行的(此時並無返回運算後的值,而是先把要返回的值保存起來,管finally中的代碼怎麼樣,返回的值都不會改變,仍然是以前保存的值),因此函數返回值是在finally執行前肯定的;

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

這裏有個有趣的問題,若是把上述中的test2方法中的finally語句塊中加上return,編譯器就會提示警告:finally block does not complete normally 

 1 public int test2(){
 2         int divider=10;
 3         int result=100;
 4         try{
 5             while(divider>-1){
 6                 divider--;
 7                 result=result+100/divider;
 8             }
 9             return result;
10         }catch(Exception e){
11             e.printStackTrace();
12             System.out.println("異常拋出了!!");
13             return result=999;
14         }finally{
15             System.out.println("這是finally,哈哈哈!!");
16             System.out.println("result的值爲:"+result);
17             return result;//編譯器警告 18         }
19         
20     }

分析問題: finally塊中的return語句可能會覆蓋try塊、catch塊中的return語句;若是finally塊中包含了return語句,即便前面的catch塊從新拋出了異常,則調用該方法的語句也不會得到catch塊從新拋出的異常,而是會獲得finally塊的返回值,而且不會捕獲異常。

解決問題:面對上述狀況,其實更合理的作法是,既不在try block內部中使用return語句,也不在finally內部使用 return語句,而應該在 finally 語句以後使用return來表示函數的結束和返回。如:

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

 總結:

  一、無論有木有出現異常或者try和catch中有返回值return,finally塊中代碼都會執行;

  二、finally中最好不要包含return,不然程序會提早退出,返回會覆蓋try或catch中保存的返回值。

  3.  e.printStackTrace()能夠輸出異常信息。

  4.  return值爲-1爲拋出異常的習慣寫法。

  5.  若是方法中try,catch,finally中沒有返回語句,則會調用這三個語句塊以外的return結果。

  6.  finally 在try中的return以後 在返回主調函數以前執行

3、throw和throws關鍵字

java中的異常拋出一般使用throw和throws關鍵字來實現。

throw ----將產生的異常拋出,是拋出異常的一個動做

通常會用於程序出現某種邏輯時程序員主動拋出某種特定類型的異常。如:
  語法:throw (異常對象),如:

1 public static void main(String[] args) { 
2     String s = "abc"; 
3     if(s.equals("abc")) { 
4       throw new NumberFormatException(); 
5     } else { 
6       System.out.println(s); 
7     } 
8     //function(); 
9 } 

運行結果:

Exception in thread "main" java.lang.NumberFormatException
at test.ExceptionTest.main(ExceptionTest.java:67)

throws----聲明將要拋出何種類型的異常(聲明)。

語法格式:

1 public void 方法名(參數列表)
2    throws 異常列表{
3 //調用會拋出異常的方法或者:
4 throw new Exception();
5 }

當某個方法可能會拋出某種異常時用於throws 聲明可能拋出的異常,而後交給上層調用它的方法程序處理。如:

 1 public static void function() throws NumberFormatException{ 
 2     String s = "abc"; 
 3     System.out.println(Double.parseDouble(s)); 
 4   } 
 5     
 6   public static void main(String[] args) { 
 7     try { 
 8       function(); 
 9     } catch (NumberFormatException e) { 
10       System.err.println("非數據類型不能轉換。"); 
11       //e.printStackTrace(); 
12     } 
13 } 

throw與throws的比較
一、throws出如今方法函數頭;而throw出如今函數體。
二、throws表示出現異常的一種可能性,並不必定會發生這些異常;throw則是拋出了異常,執行throw則必定拋出了某種異常對象。
三、二者都是消極處理異常的方式(這裏的消極並非說這種方式很差),只是拋出或者可能拋出異常,可是不會由函數去處理異常,真正的處理異常由函數的上層調用處理。

來看個例子:

throws e1,e2,e3只是告訴程序這個方法可能會拋出這些異常,方法的調用者可能要處理這些異常,而這些異常e1,e2,e3多是該函數體產生的。
throw則是明確了這個地方要拋出這個異常。如:

 1 void doA(int a) throws (Exception1,Exception2,Exception3){
 2       try{
 3          ......
 4  
 5       }catch(Exception1 e){
 6        throw e;
 7       }catch(Exception2 e){
 8        System.out.println("出錯了!");
 9       }
10       if(a!=b)
11        throw new Exception3("自定義異常");
12 }

分析:
1.代碼塊中可能會產生3個異常,(Exception1,Exception2,Exception3)。
2.若是產生Exception1異常,則捕獲以後再拋出,由該方法的調用者去處理。
3.若是產生Exception2異常,則該方法本身處理了(即System.out.println("出錯了!");)。因此該方法就不會再向外拋出Exception2異常了,void doA() throws Exception1,Exception3 裏面的Exception2也就不用寫了。由於已經用try-catch語句捕獲並處理了。
4.Exception3異常是該方法的某段邏輯出錯,程序員本身作了處理,在該段邏輯錯誤的狀況下拋出異常Exception3,則該方法的調用者也要處理此異常。這裏用到了自定義異常,該異常下面會由解釋。

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

使用throw和throws關鍵字須要注意如下幾點:

1.throws的異常列表能夠是拋出一條異常,也能夠是拋出多條異常,每一個類型的異常中間用逗號隔開

2.方法體中調用會拋出異常的方法或者是先拋出一個異常:用throw new Exception() throw寫在方法體裏,表示「拋出異常」這個動做。

3.若是某個方法調用了拋出異常的方法,那麼必須添加try catch語句去嘗試捕獲這種異常, 或者添加聲明,將異常拋出給更上一層的調用者進行處理

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

自定義異常

爲何要使用自定義異常,有什麼好處

1.咱們在工做的時候,項目是分模塊或者分功能開發的 ,基本不會你一我的開發一整個項目,使用自定義異常類就統一了對外異常展現的方式

2.有時候咱們遇到某些校驗或者問題時,須要直接結束掉當前的請求,這時即可以經過拋出自定義異常來結束,若是你項目中使用了SpringMVC比較新的版本的話有控制器加強,能夠經過@ControllerAdvice註解寫一個控制器加強類來攔截自定義的異常並響應給前端相應的信息

3.自定義異常能夠在咱們項目中某些特殊的業務邏輯時拋出異常,好比"中性".equals(sex),性別等於中性時咱們要拋出異常,而Java是不會有這種異常的。系統中有些錯誤是符合Java語法的,但不符合咱們項目的業務邏輯。

4.使用自定義異常繼承相關的異常來拋出處理後的異常信息能夠隱藏底層的異常,這樣更安全,異常信息也更加的直觀。自定義異常能夠拋出咱們本身想要拋出的信息,能夠經過拋出的信息區分異常發生的位置,根據異常名咱們就能夠知道哪裏有異常,根據異常提示信息進行程序修改。好比空指針異常NullPointException,咱們能夠拋出信息爲「xxx爲空」定位異常位置,而不用輸出堆棧信息。

說完了爲何要使用自定義異常,有什麼好處,咱們再來看看自定義異常的毛病

毋庸置疑,咱們不可能期待JVM(Java虛擬機)自動拋出一個自定義異常,也不可以期待JVM會自動處理一個自定義異常。發現異常、拋出異常以及處理異常的工做必須靠編程人員在代碼中利用異常處理機制本身完成。這樣就相應的增長了一些開發成本和工做量,因此項目不必的話,也不必定非得要用上自定義異常,要可以本身去權衡。

最後,咱們來看看怎麼使用自定義異常:

在 Java 中你能夠自定義異常。編寫本身的異常類時須要記住下面的幾點。

  • 全部異常都必須是 Throwable 的子類。
  • 若是但願寫一個檢查性異常類,則須要繼承 Exception 類。
  • 若是你想寫一個運行時異常類,那麼須要繼承 RuntimeException 類。

能夠像下面這樣定義本身的異常類:

class MyException extends Exception{ }

 

咱們來看一個實例:

 1 package com.hysum.test;
 2 
 3 public class MyException extends Exception {
 4      /**
 5      * 錯誤編碼
 6      */
 7     private String errorCode;
 8 
 9    
10     public MyException(){}
11     
12     /**
13      * 構造一個基本異常.
14      *
15      * @param message
16      *        信息描述
17      */
18     public MyException(String message)
19     {
20         super(message);
21     }
22 
23    
24 
25     public String getErrorCode() {
26         return errorCode;
27     }
28 
29     public void setErrorCode(String errorCode) {
30         this.errorCode = errorCode;
31     }
32 
33     
34 }

使用自定義異常拋出異常信息:

 1 package com.hysum.test;
 2 
 3 public class Main {
 4 
 5     public static void main(String[] args) {
 6         // TODO Auto-generated method stub
 7         String[] sexs = {"男性","女性","中性"};
 8                   for(int i = 0; i < sexs.length; i++){
 9                       if("中性".equals(sexs[i])){
10                           try {
11                             throw new MyException("不存在中性的人!");
12                         } catch (MyException e) {
13                             // TODO Auto-generated catch block
14                             e.printStackTrace();
15                         }
16                      }else{
17                          System.out.println(sexs[i]);
18                      }
19                 } 
20     }
21 
22 }

運行結果:

 就是這麼簡單,能夠根據實際業務需求去拋出相應的自定義異常。

4、java中的異常鏈

異常須要封裝,可是僅僅封裝仍是不夠的,還須要傳遞異常

異常鏈是一種面向對象編程技術,指將捕獲的異常包裝進一個新的異常中並從新拋出的異常處理方式。原異常被保存爲新異常的一個屬性(好比cause)。這樣作的意義是一個方法應該拋出定義在相同的抽象層次上的異常,但不會丟棄更低層次的信息

我能夠這樣理解異常鏈:

把捕獲的異常包裝成新的異常,在新異常裏添加原始的異常,並將新異常拋出,它們就像是鏈式反應同樣,一個致使(cause)另外一個。這樣在最後的頂層拋出的異常信息就包括了最底層的異常信息。

》場景

好比咱們的JEE項目通常都又三層:持久層、邏輯層、展示層,持久層負責與數據庫交互,邏輯層負責業務邏輯的實現,展示層負責UI數據的處理。

有這樣一個模塊:用戶第一次訪問的時候,須要持久層從user.xml中讀取數據,若是該文件不存在則提示用戶建立之,那問題就來了:若是咱們直接把持久層的異常FileNotFoundException拋棄掉,邏輯層根本無從得知發生任何事情,也就不能爲展示層提供一個友好的處理結果,最終倒黴的就是展示層:沒有辦法提供異常信息,只能告訴用戶「出錯了,我也不知道出了什麼錯了」—毫無友好性而言。

正確的作法是先封裝,而後傳遞,過程以下:

 1.把FileNotFoundException封裝爲MyException。

    2.拋出到邏輯層,邏輯層根據異常代碼(或者自定義的異常類型)肯定後續處理邏輯,而後拋出到展示層。

    3.展示層自行肯定展示什麼,若是管理員則能夠展示低層級的異常,若是是普通用戶則展現封裝後的異常。

》示例

 1 package com.hysum.test;
 2 
 3 public class Main {
 4     public void test1() throws RuntimeException{
 5         String[] sexs = {"男性","女性","中性"};
 6         for(int i = 0; i < sexs.length; i++){
 7             if("中性".equals(sexs[i])){
 8                 try {
 9                     throw new MyException("不存在中性的人!");
10                 } catch (MyException e) {
11                     // TODO Auto-generated catch block
12                     e.printStackTrace();
13                     RuntimeException rte=new RuntimeException(e);//包裝成RuntimeException異常
14                     //rte.initCause(e);
15                     throw rte;//拋出包裝後的新的異常
16                 }
17            }else{
18                System.out.println(sexs[i]);
19            }
20       } 
21     }
22     public static void main(String[] args) {
23         // TODO Auto-generated method stub
24         Main m =new Main();
25         
26         try{
27         m.test1();
28         }catch (Exception e){
29             e.printStackTrace();
30             e.getCause();//得到原始異常
31         }
32         
33     }
34 
35 }

運行結果:

結果分析:咱們能夠看到控制檯先是輸出了原始異常,這是由e.getCause()輸出的;而後輸出了e.printStackTrace(),在這裏能夠看到Caused by:原始異常和e.getCause()輸出的一致。這樣就是造成一個異常鏈。initCause()的做用是包裝原始的異常,當想要知道底層發生了什麼異常的時候調用getCause()就能得到原始異常。 

》建議

異常須要封裝和傳遞,咱們在進行系統開發的時候,不要「吞噬」異常,也不要「赤裸裸」的拋出異常,封裝後在拋出,或者經過異常鏈傳遞,能夠達到系統更健壯、友好的目的。

5、結束語

java的異常處理的知識點雜並且理解起來也有點困難,我在這裏給你們總結了如下幾點使用java異常處理的時候,良好的編碼習慣:

一、處理運行時異常時,採用邏輯去合理規避同時輔助try-catch處理

二、在多重catch塊後面,能夠加一個catch(Exception)來處理可能會被遺漏的異常

三、對於不肯定的代碼,也能夠加上try-catch,處理潛在的異常

四、儘可能去處理異常,切記只是簡單的調用printStackTrace()去打印

五、具體如何處理異常,要根據不一樣的業務需求和異常類型去決定

六、儘可能添加finally語句塊去釋放佔用的資源

 

參考博文:

http://blog.csdn.net/p106786860/article/details/11889327

http://www.cnblogs.com/AlanLee/p/6104492.html

http://blog.csdn.net/luoweifu/article/details/10721543

相關文章
相關標籤/搜索