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
以上是关于Java的异常处理的主要内容,如果未能解决你的问题,请参考以下文章
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异常(代码片段
PCL异常处理:pcl 1.8.13rdpartyoostincludeoost-1_64oost ypeofmsvc ypeof_impl.hpp(125): error(代码片段