Java的异常处理

Posted 空心小木头

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java的异常处理相关的知识,希望对你有一定的参考价值。

一、介绍

  在认为编写的程序中不可能没有一点错误,当程序运行时,发生了编写者不希望的事件,阻止了程序的正常执行,这就是异常。当异常出现时,我们处理的方式就是本文要介绍的主要内容。

  Java的异常处理机制:

在Java中异常被当作对象来处理,其根类为java.lang.Throwable,Throwable又派生出Error类和Exception类。

 

总体上我们根据Javac对异常的处理要求,将异常类分为2类。

非检查异常(unckecked exception):Error 和 RuntimeException 以及他们的子类。javac在编译时,不会提示和发现这样的异常,不要求在程序处理这些异常。所以如果愿意,我们可以编写代码处理(使用try...catch...finally)这样的异常,也可以不处理。对于这些异常,我们应该修正代码,而不是去通过异常处理器处理 。这样的异常发生的原因多半是代码写的有问题。如除0错误ArithmeticException,错误的强制类型转换错误ClassCastException,数组索引越界ArrayIndexOutOfBoundsException,使用了空对象NullPointerException等等。

 

检查异常(checked exception):除了Error 和 RuntimeException的其它异常。javac强制要求程序员为这样的异常做预备处理工作(使用try...catch...finally或者throws)。在方法中要么用try-catch语句捕获它并处理,要么用throws子句声明抛出它,否则编译不会通过。这样的异常一般是由程序的运行环境导致的。因为程序可能被运行在各种未知的环境下,而程序员无法干预用户如何使用他编写的程序,于是程序员就应该为这样的异常时刻准备着。如SQLException , IOException,ClassNotFoundException 等。

需要明确的是:检查和非检查是对于javac来说的,这样就很好理解和区分了。

二、异常的基本处理方式

  先看一个处理异常的例子:

 public class yichang 
     public static void main(String[] args) 
         chufa x = new chufa();
         x.chufa1(1,0);
     
 
 class chufa
 
     public int chufa1(int a,int b)
         int c = 0;
         try 
             c = a/b;
          catch (Exception e) 
             e.printStackTrace();
         
         return c;
     
 
java.lang.ArithmeticException: / by zero
    at chufa.chufa1(yichang.java:14)
    at yichang.main(yichang.java:6)

  这里是一个十分基础的异常处理,在try中的语句发生了异常无法正常往下执行的时候,就会执行catch,而catch这里有一个Exception e,这是一个异常对象,而在catch里面执行了这个e对象printStackTrace(),这个方法的作用是将异常抛出,让调用这个方法的地方去处理这个异常。

  从输出可以看出,在try语句中出现异常的时候,catch抛出异常,而抛出这个异常就到了main函数里(mian函数调用了chufa),所以在输出的第三行也显示了mian函数出现异常,这可以理解为异常从出现的地方找到了最近调用它的函数,将异常抛给它,类似于异常的冒泡,一切设计到异常的地方都会被显示。

 

那如果这里我们不用异常捕获,编译器并不知道这个地方要发生异常,也不会强制我们添加异常捕获,因为这个异常是非检查异常

 

再看看检查异常下面例子:

  public void testException()
         
         //FileInputStream的构造函数会抛出FileNotFoundException
         FileInputStream fileIn=new FileInputStream("E:\\\\a.txt");
 
         int word;
         //read方法会抛出IOException
         while((word=fileIn.read())!=-1)
         
         System.out.print((char)word);
         
         //close方法会抛出IOException
         fileIn.close();
         

这段代码是不能通过编译的,因为里面可能会出现检查异常,编译器会强制编写者对异常进行处理

 

在Java中除了可以用try catch来对异常进行捕获和处理之外,还有其他两种方法,共有三种:

1、上文提到try catch

2、可以在方法体外用throws进行抛出声明,这个抛出会告知调用这个方法的对象,这个方法可能会出现的异常,这里也有两种情况:

  • 如果抛出的是非处理异常,调用这个方法的对象可以选择性的进行异常处理,也就是编译器不会因为不处理这个异常而报错
  • 如果抛出的是处理异常,那调用这个方法的对象就必须显式的处理这个异常,将其抛出到更高层,或者进行其他操作

3、在代码块中用throw手动抛出一个异常对象,此时也会有两种情况:

  • 如果抛出的是非处理异常,调用这个方法的对象可以选择性的进行异常处理,也就是编译器不会因为不处理这个异常而报错
  • 如果抛出的是处理异常,那调用这个方法的对象就必须显式的处理这个异常,将其抛出到更高层,或者进行其他操作(与上面一样)

(如果最终将异常抛给main方法,则相当于交给jvm自动处理,此时jvm会简单地打印异常信息)

 

三、深入认识几种异常处理的方式

1、Try,catch,finally

  这里又多出一个fianlly关键字,try关键字可以配合剩余两个关键字使用,三个关键字有几种组合方式:

(1)try代码块                           
     catch代码块  

(2)try代码块
    finally代码块

(3)try代码块
    catch代码块
      finally代码块   

   这里try是用来捕获异常,catch是当出现异常时执行的操作,finally不管try里是否出现异常都会执行,catch块可以有多个,try和finally只能有一个,同时finally也可以不添加。

  当有多个catch块的时候,是按照catch块的先后顺序进行匹配,一旦异常被一个catch块匹配,则不会与后面的catch块进匹配——根据这个我们得出,当一个catch块能捕捉异常的范围要大于其他catch块,大范围的应该放在后面进行匹配,如果把大范围的放在前面,那范围小的在后面永远不可能被匹配

 

那再看如下代码:

 class test
     public static void main(String[] args) 
         TestException t = new TestException();
         String x = t.chufa(1,0);
         System.out.println(x);
     
 
 class TestException
     public String chufa(int a,int b)
         int p = 0;
         try 
             p = a/b;
          catch (Exception e) 
             return "This is catch";
         finally 
             return "This is finally";
         
     
 

可以考虑一下这一段代码的输出:

This is finally

这三个关键字原本的执行顺序应该是:

try内代码块若没有出现异常,不会执行catch,直接执行finally

try内代码块若出现异常,会先执行catch进行处理异常的操作,然后再执行finally

 

  我们可以看上面的例子,try里面出现了异常,这里肯定先执行了catch,但catch里的语句直接返回了,按理应该不会执行finally,但最终结果却是返回的是finally的返回值。

  从这里可以看出,无论前面是否有return语句,finally块的语句都会执行。

 

我们稍微修改一下上面的例子

  class x
     public static void main(String[] args) 
         TestException t = new TestException();
         String x = t.chufa(1,0);
         System.out.println(x);
     
 
 class TestException
     public String chufa(int a,int b)
         int p = 0;
         try 
             p = a/b;
          catch (Exception e) 
             System.out.println("到catch");//试验是否执行catch
             return "This is catch";
         finally 
             return "This is finally";
         
     
 
到catch
This is finally

  这里我们彻底明白了,catch是先于finally执行,但catch里要返回了,编译器就直接先执行了finally的代码块,让程序直接返回,如果finally里面不是返回值,则程序还是会在catch里进行返回

  所以finally执行的语句可能会覆盖我们到返回值,所以千万不要在finally写任何返回语句,finally主要的作用就是进行资源的释放。

2、throws和thow关键字

  throws用在方法声明的地方,表明这个方法可能会抛出某种异常,然后将异常交给调用其到程序来处理,throws后面可以跟着多个异常。

 

  throw是一定会抛出异常,这是当程序执行到某处的时候编写者主动去抛出特定异常,只能用在方法体中,而throw一个很重要的作用就是可以进行异常的转换,可以抛出想抛出的信息。

 

3、继承关系中的异常

本小节讨论子类重写父类方法的时候,如何确定异常抛出声明的类型。下面是三点原则:

  1)父类的方法没有声明异常,子类在重写该方法的时候不能声明异常;

  2)如果父类的方法声明一个异常exception1,则子类在重写该方法的时候声明的异常不能是exception1的父类;

  3)如果父类的方法声明的异常类型只有非运行时异常(运行时异常),则子类在重写该方法的时候声明的异常也只能有非运行时异常(运行时异常),不能含有运行时异常(非运行时异常)。

 

4、异常处理中的建议

   以下是根据前人总结的一些异常处理的建议:

1.只在必要使用异常的地方才使用异常,不要用异常去控制程序的流程

  谨慎地使用异常,异常捕获的代价非常高昂,异常使用过多会严重影响程序的性能。如果在程序中能够用if语句和Boolean变量来进行逻辑判断,那么尽量减少异常的使用,从而避免不必要的异常捕获和处理。比如下面这段经典的程序:

 public void useExceptionsForFlowControl()   
   try   
   while (true)   
     increaseCount();  
       
    catch (MaximumCountReachedException ex)   
     
   //Continue execution  
   
     
 public void increaseCount() throws MaximumCountReachedException   
   if (count >= 5000)  
     throw new MaximumCountReachedException();  
 

  上边的useExceptionsForFlowControl()用一个无限循环来增加count直到抛出异常,这种做法并没有说让代码不易读,而是使得程序执行效率降低

2.切忌使用空catch块

  在捕获了异常之后什么都不做,相当于忽略了这个异常。千万不要使用空的catch块,空的catch块意味着你在程序中隐藏了错误和异常,并且很可能导致程序出现不可控的执行结果。如果你非常肯定捕获到的异常不会以任何方式对程序造成影响,最好用Log日志将该异常进行记录,以便日后方便更新和维护。

3.检查异常和非检查异常的选择

一旦你决定抛出异常,你就要决定抛出什么异常。这里面的主要问题就是抛出检查异常还是非检查异常。

  检查异常导致了太多的try…catch代码,可能有很多检查异常对开发人员来说是无法合理地进行处理的,比如SQLException,而开发人员却不得不去进行try…catch,这样就会导致经常出现这样一种情况:逻辑代码只有很少的几行,而进行异常捕获和处理的代码却有很多行。这样不仅导致逻辑代码阅读起来晦涩难懂,而且降低了程序的性能。

  我个人建议尽量避免检查异常的使用,如果确实该异常情况的出现很普遍,需要提醒调用者注意处理的话,就使用检查异常;否则使用非检查异常。

  因此,在一般情况下,我觉得尽量将检查异常转变为非检查异常交给上层处理。

5.不要将提供给用户看的信息放在异常信息里

 public class Main 
     public static void main(String[] args) 
         try 
             String user = null;
             String pwd = null;
             login(user,pwd);
          catch (Exception e) 
             System.out.println(e.getMessage());
         
          
     
      
     public static void login(String user,String pwd) 
         if(user==null||pwd==null)
             throw new NullPointerException("用户名或者密码为空");
         //...
     
 

  展示给用户错误提示信息最好不要跟程序混淆一起,比较好的方式是将所有错误提示信息放在一个配置文件中统一管理。

6.避免多次在日志信息中记录同一个异常

  只在异常最开始发生的地方进行日志信息记录。很多情况下异常都是层层向上跑出的,如果在每次向上抛出的时候,都Log到日志系统中,则会导致无从查找异常发生的根源。

7. 异常处理尽量放在高层进行

  尽量将异常统一抛给上层调用者,由上层调用者统一之时如何进行处理。如果在每个出现异常的地方都直接进行处理,会导致程序异常处理流程混乱,不利于后期维护和异常错误排查。由上层统一进行处理会使得整个程序的流程清晰易懂。

8. 在finally中释放资源

  如果有使用文件读取、网络操作以及数据库操作等,记得在finally中释放资源。这样不仅会使得程序占用更少的资源,也会避免不必要的由于资源未释放而发生的异常情况。 
   

 参考资料:

https://www.cnblogs.com/dolphin0520/p/3769804.html

 

https://www.cnblogs.com/lulipro/p/7504267.html

 

11

以上是关于Java的异常处理的主要内容,如果未能解决你的问题,请参考以下文章

使用片段中的处理程序时出现非法状态异常

异常和TCP通讯

java.util.MissingResourceException: Can't find bundle for base name init, locale zh_CN问题的处理(代码片段

java.lang.NullPointerException: Attempt to invoke virtual method ‘int android.database.sqlite异常(代码片段

java异常 throw和try-catch的关系

PCL异常处理:pcl 1.8.13rdpartyoostincludeoost-1_64oost ypeofmsvc ypeof_impl.hpp(125): error(代码片段