JAVA异常与异常处理详解

1、异常简介

什么是异常?前端

异常就是有异于常态,和正常状况不同,有错误出错。在java中,阻止当前方法或做用域的状况,称之为异常。java

java中异常的体系是怎么样的呢?mysql

1.Java中的全部不正常类都继承于Throwable类。Throwable主要包括两个大类,一个是Error类,另外一个是Exception类;程序员

    

 2.其中Error类中包括虚拟机错误和线程死锁,一旦Error出现了,程序就完全的挂了,被称为程序终结者;sql

   

3.Exception类,也就是一般所说的“异常”。主要指编码、环境、用户操做输入出现问题,Exception主要包括两大类,非检查异常(RuntimeException)和检查异常(其余的一些异常)数据库

    

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语句块去释放占用的资源

相关文章
相关标签/搜索