首頁 > Java > java教程 > 主體

JAVA中異常的詳細介紹

零下一度
發布: 2017-07-17 09:46:25
原創
1428 人瀏覽過

一、異常簡介

異 常:

異常:就是不正常。程式在運作時出現的不正常情況。其實就是程式中出現的問題。這個問題按照物件導向思想來描述,並封裝成了物件。因為問題的產生有產生的原因、有問題的名稱、有問題的描述等多個屬性資訊存在。當出現多屬性資訊最方便的方式就是將這些資訊進行封裝。異常就是Java依照物件導向的想法將問題進行物件封裝。這樣就方便操作問題以及處理問題。

出現的問題有很多種,例如角標越界,空指標等都是。就對這些問題進行分類。而且這些問題都有共通性內容例如:每一個問題都有名稱,同時還有問題描述的訊息,問題出現的位置,所以可以不斷的向上抽取。形成了異常體系。 

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

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

    

 2.其中Error類中包括虛擬機錯誤## 2.其中Error類中包括虛擬機錯誤線程死鎖,一旦Error出現了,程式就徹底的掛了

,被稱為

程式終結者;

   

3.Exception類,也就是通常所說的「異常」。主要指編碼、環境、使用者操作輸入出現問題,Exception主要包括兩大類別

,非檢查異常(RuntimeException)和檢查異常(其他的一些異常)

    4.RuntimeException異常主要包括以下四種異常(其實還有很多其他異常,這裡不一一列出):空指標異常、陣列下標越界異常、型別轉換異常、算術異常

。 RuntimeException異常會由java虛擬機器自動拋出並自動捕獲

(就算我們沒寫異常捕獲語句運行時也會拋出錯誤!!),此類異常的出現絕大數情況是程式碼本身有問題應該從邏輯上去解決並改進程式碼。

  

5.檢查異常,造成該異常的原因多種多樣,比如說檔案不存在、或者是連接錯誤等等。跟它的「兄弟」RuntimeException運行異常不同,

該異常我們必須手動在程式碼裡添加捕獲語句來處理該異常,這也是我們學習java異常語句中主要處理的異常對象。


  

二、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      * 如果:捕获异常,打印输出“异常抛出了”,返回-110      * 否则:返回result11      * @return12      */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 stub30         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      * @return10      */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 stub35         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 

  public int test2(){ 
          int divider=10; 
          int result=100; 
          try{ 
              while(divider>-1){ 
                  divider--; 
                  result=result+100/divider; 
              } 
              return result;10         }catch(Exception e){
              e.printStackTrace();
              System.out.println("异常抛出了!!");
              return result=999;14         }finally{
              System.out.println("这是finally,哈哈哈!!");
              System.out.println("result的值为:"+result);
              return result;//编译器警告18         }
          
      }
登入後複製

分析问题: finally块中的return语句可能会覆盖try块、catch块中的return语句;如果finally块中包含了return语句,即使前面的catch块重新抛出了异常,则调用该方法的语句也不会获得catch块重新抛出的异常,而是会得到finally块的返回值,并且不会捕获异常。

解决问题:面对上述情况,其实更合理的做法是,既不在try block内部中使用return语句,也不在finally内部使用 return语句,而应该在 finally 语句之后使用return来表示函数的结束和返回。如:

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

 总结:

  1、不管有木有出现异常或者try和catch中有返回值return,finally块中代码都会执行;

  2、finally中最好不要包含return,否则程序会提前退出,返回会覆盖try或catch中保存的返回值。

  3.  e.printStackTrace()可以输出异常信息。

  4.  return值为-1为抛出异常的习惯写法。

  5.  如果方法中try,catch,finally中没有返回语句,则会调用这三个语句块之外的return结果。

  6.  finally 在try中的return之后 在返回主调函数之前执行


三、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的比较
1、throws出现在方法函数头;而throw出现在函数体。
2、throws表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某种异常对象。
3、两者都是消极处理异常的方式(这里的消极并不是说这种方式不好),只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理。

来看个例子:

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 message16      *        信息描述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 block14                             e.printStackTrace();15                         }16                      }else{17                          System.out.println(sexs[i]);18                      }19                 } 
20     }21 22 }
登入後複製

运行结果:

就是这么简单,可以根据实际业务需求去抛出相应的自定义异常。


四、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 block12                     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 stub24         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()就能獲得原始異常

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ;>>>>>>>>>>>>>>>>>>>>>>>>> ;>>

》建議

#異常需要封裝和傳遞,我們在進行系統開發的時候,不要「吞噬」異常,也不要「赤裸裸」的拋出異常,封裝後在拋出,或透過異常鏈傳遞,可以達到系統更健壯、友善的目的。


五、結束語

java的異常處理的知識點雜而且理解起來也有點困難,我在這裡給大家總結了以下幾點使用java異常處理的時候,

良好的編碼習慣:

1、處理執行階段例外時,採用邏輯去合理規避同時輔助try-catch處理

2、在多重catch區塊後面,可以加上一個catch(Exception)來處理可能會被遺漏的例外

3、對於不確定的程式碼,也可以加上try-catch,處理潛在的異常

4、盡量去處理異常,切記只是簡單的呼叫printStackTrace()去列印

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

6、盡量加入finally語句區塊去釋放佔用的資源

以上是JAVA中異常的詳細介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
上一篇:Java的三大特性--封裝、繼承、多型 下一篇:Spring+SpringMVC+MyBatis深入學習及建構(十七)-SpringMVC攔截器
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
最新問題
相關專題
更多>
熱門推薦
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板