Java——Exception

异常情形是指阻止当前方法或者作用域继续执行的问题。比如用户输入了非法数据、要打开的文件不存在、网络通信时连接中断,或者JVM内存溢出等都可能导致异常。通过后面的异常类型和常见异常的介绍,可以知道产生的异常的原因有很多,有可能是用户不当的操作或者程序中的逻辑错误,也有可能是JVM等物理错误产生的。

异常类型

检查性异常

最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。常见的检查性异常有ClassNotFoundException,CloneNotSupportedException,IllegalAccessException,InstantiationException,InterruptedException,NoSuchFieldException,NoSuchMethodException等。

运行时异常

运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。该类异常大多是RuntimeException类及其子类异常,比如ArithmeticException,ArrayIndexOutOfBoundsException,ClassCastException,IllegalArgumentException,IndexOutOfBoundsException,NullPointerException,NumberFormatException,StringIndexOutOfBoundsException等。

错误

错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如栈溢出,JVM内存溢出,Java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等,它们在编译时也检查不到的。

异常类

Throwable是java语言中所有错误和异常的超类,它有两个子类:Error、Exception。

Error

其中Error为错误,是程序无法处理的,如OutOfMemoryError、ThreadDeath等,出现这种情况你唯一能做的就是听之任之,交由JVM来处理,不过JVM在大多数情况下会选择终止线程。

Exception

而Exception是程序可以处理的异常。它又分为两种:CheckedException(检查性异常)和UncheckedException(非检查性异常)。其中CheckException发生在编译阶段,必须要使用try…catch或者throws子句声明抛出,否则编译不通过。而UncheckedException发生在运行期,具有不确定性,主要是由于程序的逻辑问题所引起的。

异常和错误的区别:异常能被程序本身可以处理,错误是无法处理的。

异常类继承关系图如下
这里写图片描述
图中红色部分为受检查异常,它们必须被捕获,或者在函数中声明为抛出该异常。图片来自: http://www.importnew.com/11725.html

异常处理机制

在 Java 应用程序中,异常处理机制为:抛出异常,捕捉异常。

捕获异常

在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器(exception handler)。潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合。当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适 的异常处理器。运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行。当运行时系统遍历调用栈而未找到合适 的异常处理器,则运行时系统终止。同时,意味着Java程序的终止。

捕捉异常通过try-catch语句或者try-catch-finally语句实现。如果有多个catch子句,应该尽量将捕获底层异常类的catch子句放在前面,同时尽量将捕获相对高层的异常类的catch子句放在后面,否则,捕获底层异常类的catch子句将可能会被屏蔽。一旦某个catch捕获到匹配的异常类型,将进入异常处理代码。一经处理结束,就意味着整个try-catch语句结束,其他的catch子句不再有匹配和捕获异常类型的机会。

try 块:用于捕获异常。其后可接零个或多个catch块,如果没有catch块,则必须跟一个finally块。

finally 块:无论是否捕获或处理异常,finally块里的语句都会被执行。当在try块或catch块中遇到return语句时,finally语句块将在方法返回之前被执行。在以下4种特殊情况下,finally块不会被执行:

  1. 在finally语句块中发生了异常

  2. 在前面的代码中用了System.exit()退出程序

  3. 程序所在的线程死亡

  4. 关闭CPU

对捕获的异常如何处理:

  1. 处理异常。对所发生的的异常进行一番处理,如修正错误、提醒、写入日志等

  2. 重新抛出异常。直接上抛异常,给调用方处理

  3. 封装异常。自定义异常,对原来的异常信息进行分类,然后进行封装处理

抛出异常

当一个方法出现错误引发异常时,方法创建异常对象并交付运行时系统,异常对象中包含了异常类型和异常出现时的程序状态等异常信息,运行时系统负责寻找处置异常的代码并执行。抛出异常可以使用throw和throws实现。

  1. throws是方法抛出异常。在方法声明中,如果添加了throws子句,表示该方法即将抛出异常,异常的处理交由它的调用者。调用方法必须遵循任何可检查性异常的处理和声明规则,比如若覆盖一个方法,则不能声明与覆盖方法不同的异常,声明的任何异常必须是被覆盖方法所声明异常的同类或子类。

  2. throw是语句抛出异常。它不可以单独使用,要么与try/catch一起使用,要么与throws一起使用。

异常链

Java 这种通过 throws 向上传递异常信息的处理机制就会形成异常链。在异常链的使用中,throw 抛出的是一个新的异常信息,这样势必会导致原有的异常信息丢失,如何保持?在Throwable及其子类中的构造器中都可以接受一个 cause 参数,该参数保存了原有的异常信息,通过 getCause() 就可以获取该原始异常信息。

自定义异常

用户自定义异常类,只需继承Exception类即可。在程序中使用自定义异常类,大体可分为以下几个步骤。

(1)创建自定义异常类。

(2)在方法中通过 throw 关键字抛出异常对象。

(3)如果在当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理;否则在方法的声明处通过 throws 关键字指明要抛出给方法调用者的异常,继续进行下一步操作。

(4)在出现异常方法的调用者中捕获并处理异常。

一般情况下自定义检查性异常比较少,常见是根据业务逻辑自定义一些运行时异常,并给这些异常分配errorcode 和自定义的异常封装信息。

实践小结

  1. 不要在finally块中处理返回值。如果try中有return语句,会将return的值存在其他地方,finally中对return的值的修改并未作用到try中的return值上。

  2. 不要在构造函数中抛出异常。

  3. 不要将将异常直接显示在页面或客户端,可以自定义异常类,将异常分配errorcode并可以自定义异常封装信息。

  4. 避免对代码层次结构的污染,比如dao层的sql异常就在dao层来处理

  5. 异常处理占用系统资源,不要将异常包含在循环语句块中,有时候处理的不得当还有可能造成死循环(比如在某次循环中发生异常导致异常后面的代码无法执行以致于不能达到循环结束条件)

  6. 避免多层次封装抛出非检查性异常。比如将所有的 Exception 再转换成 RuntimeException 抛出,如果 Exception 的类型已经是 RuntimeException 时,又做了一次封装将丢失了原有的 RuntimeException 携带的有效信息。解决办法是判断捕获的异常是不是 RuntimeException 的实例。如果是,将拷贝相应的属性到新建的实例上;或者用不同的 catch 语句块捕捉 RuntimeException 和其它的 Exception。

  7. 多层次打印异常。其实打印日志只需要在代码的最外层打印就可以了,异常打印也可以写成 AOP,织入到框架的最外层,或者可以利用拦截器或者过滤器实现日志的打印,降低代码维护、迁移的成本。可以将参数信息添加到异常信息中,方便问题定位。

  8. 发现程序中潜在的异常。在写代码的过程中,由于对调用代码缺乏深层次的了解,不能准确判断是否调用的代码会产生异常,因而忽略处理。在产生了 Production Bug 之后才想起来应该在某段代码处添加异常捕获,甚至不能准确指出出现异常的原因。这就需要开发人员不仅知道自己在做什么,而且要去尽可能的知道别人做了什么,可能会导致什么结果,从全局去考虑整个应用程序的处理过程。这些思想会影响我们对代码的编写和处理。

参考的第一篇文章中的题目也挺有意思的。


[参考资料]

深入理解java异常处理机制
Java 异常处理的误区和经验总结

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值