概念与分类
Java异常基本概念
Java的基本理念是"结构不佳的代码不能运行"。发现错误的理想时机是在编译阶段,也就是在视图运行程序之前。但编译期间并不能找出所有错误,余下的问题必须在运行期间解决。这就需要错误源能通过某种方式,把适当的信息传递给某个接收者–该接受者将知道如何正确处理这个问题。 改进的错误恢复机制是提供代码健壮性的最强有力的方式。Java使用异常来提供一致的错误报告模型,使得构件能够与客户端代码可靠地沟通问题。
用强制规定的形式来消除错误处理过程中随心所欲的因素,是异常的目的之一。“异常”(exception)这个词有“我对此感到意外”的意思。问题出现了,你也许不清楚该如何处理,但你的确知道不应该置之不理;你要停下来,看看是不是有别人或是在别的地方,能够处理这个问题。只是你在当前的环境(currentcontext)中没有足够的信息来解决这个问题,所以你就把这个问题提交到一个更高级别的环境中,这里将有人作出正确的决定(有点像军队里的指挥系统)。使用异常所带来的另一个相当明显的好处是,它能使错误处理代码变得更有条理。
“异常情形”(exceptional condition)是指引发阻止当前方法或作用域继续执行的问题。把异常情形与普通问题相区分很重要,这里的普通问题是指,你在当前环境下能得到足够的信息,总能处理这个错误。而对于异常情形,你就不能继续下去了,因为你在当前环境下无法获得必要的信息来解决问题。你所能做的就是从当前的环境中跳出,并且把问题提交给上一级别的环境。这就是抛出异常时所发生的事情。当你抛出异常后,有几件事会随之发生。首先,同Java中其它对象的创建一样,将使用new在堆上创建异常对象。然后,当前的执行路径(你不能继续下去了)被终止,并且从当前环境中弹出异常对象的引用。此时,异常处理机制接管程序,并开始寻找一个恰当的地方来继续执行程序。这个恰当的地方就是“异常处理程序”(exceptionhandler),它的任务是将程序从错误状态中恢复:以使程序能要么换一种方式运行,要么继续运行下去。
常常会再捕获一个异常后跑出另外一个异常,并且希望把异常原始信息保存下来,这被称为异常链。在JDK1.4以前,程序员必须自己编写代码来保存原始异常信息,现在所有Throwable的子类子构造器中都可以接受一个cause对象作为参数,这个cause就异常原由,代表着原始异常,即使在当前位置创建并抛出行的异常,也可以通过这个cause追踪到异常最初发生的位置。
异常分类与层次
1.Throwable
Throwable是Java语言中所有错误或异常的超类。Throwable包含两个子类:Error和Exception。它们通常用于指示发生了异常情况。Throwable包含了其线程创建时线程执行堆栈的快照,它提供了printStackTrace()等接口用于获取堆栈跟踪数据等信息。
2.Exception
Exception及其子类是Throwable的一种形式,它指出了合理的应用程序想要捕获的条件。
3.RuntimeException
RuntimeException是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。
编译器不会检查RuntimeException异常。例如,除数为零时,抛出ArithmeticException异常。RuntimeException是ArithmeticException的超类。当代码发生除数为零的情况时,倘若既"没有通过throws声明抛出ArithmeticException异常",也"没有通过try…catch…处理该异常",也能通过编译。这就是我们所说的"编译器不会检查RuntimeException异常"!如果代码会产生RuntimeException异常,则需要通过修改代码进行避免。例如,若会发生除数为零的情况,则需要通过代码避免该情况的发生!
4.Error
和Exception一样,Error也是Throwable的子类。它用于指示合理的应用程序不应该试图捕获的严重问题,大多数这样的错误都是异常条件。 和RuntimeException一样,编译器也不会检查Error。
Java将可抛出(Throwable)的结构分为三种类型:被检查的异常(Checked Exception),运行时异常(RuntimeException)和错误(Error)。
(01) 运行时异常
定义: RuntimeException及其子类都被称为运行时异常。
特点: Java编译器不会检查它。也就是说,当程序中可能出现这类异常时,倘若既"没有通过throws声明抛出它",也"没有用try-catch语句捕获它",还是会编译通过。例如,除数为零时产生的ArithmeticException异常,数组越界时产生的IndexOutOfBoundsException异常,fail-fail机制产生的ConcurrentModificationException异常等,都属于运行时异常。
虽然Java编译器不会检查运行时异常,但是我们也可以通过throws进行声明抛出,也可以通过try-catch对它进行捕获处理。
如果产生运行时异常,则需要通过修改代码来进行避免。例如,若会发生除数为零的情况,则需要通过代码避免该情况的发生!
(02) 被检查的异常
定义: Exception类本身,以及Exception的子类中除了"运行时异常"之外的其它子类都属于被检查异常。
特点: Java编译器会检查它。此类异常,要么通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则不能通过编译。例如,CloneNotSupportedException就属于被检查异常。当通过clone()接口去克隆一个对象,而该对象对应的类没有实现Cloneable接口,就会抛出CloneNotSupportedException异常。
被检查异常通常都是可以恢复的。
(03) 错误
定义: Error类及其子类。
特点: 和运行时异常一样,编译器也不会对错误进行检查。
当资源不足、约束失败、或是其它程序无法继续运行的条件发生时,就产生错误。程序本身无法修复这些错误的。例如,VirtualMachineError就属于错误。
按照Java惯例,我们是不应该是实现任何新的Error子类的!
对于上面的3种结构,我们在抛出异常或错误时,到底该哪一种?《Effective Java》中给出的建议是:对于可以恢复的条件使用被检查异常,对于程序错误使用运行时异常
其他相关知识
-
异常与错误码
在异常机制出现之前,应用程序普遍采用错误代码的方式来通知调用之发生了异常。为什么要用抛出异常代替返回错误代码呢?
当我们想要告示调用者更多细节的时候,就需要与调用者约定更多的错误代码。于是。错误代码飞速膨胀,直到看起来似乎无法维护,因为我们总是查找并确认错误代码。
抛出异常可以让API层try catch 捕获异常,并且在发生问题时候,迅速定位到某行代码,异常链占用内存大,返回错误码,可以让API解析错误码,做某些操作,但是不利于定位服务端代码错误。 -
吞异常
finally语句中return或者抛出异常,trycatch中的异常会被吞掉。 -
suppressed exception
在java7中为Throwable类增加addSuppressed方法。当一个异常被抛出的时候,可能有其他异常因为该异常而被抑制住,从而无法正常抛出。这时可以通过addSuppressed方法把这些被抑制的方法记录下来。被抑制的异常会出现在抛出的异常的堆栈信息中,也可以通过getSuppressed方法来获取这些异常。这样做的好处是不会丢失任何异常,方便开发人员进行调试。 -
Java7 try-with-resource
将外部资源的句柄对象的创建放在try关键字后面的括号中,当这个try-catch代码块执行完毕后,Java会确保外部资源的close方法被调用。try-with-resource并不是JVM虚拟机的新增功能,只是JDK实现了一个语法糖,当你将上面代码反编译后会发现,其实对JVM虚拟机而言,它看到的依然是之前的写法.
1、当一个外部资源的句柄对象实现了AutoCloseable接口,JDK7中便可以利用try-with-resource语法更优雅的关闭资源,消除板式代码。
2、try-with-resource时,如果对外部资源的处理和对外部资源的关闭均遭遇了异常,“关闭异常”将被抑制,“处理异常”将被抛出,但“关闭异常”并没有丢失,而是存放在“处理异常”的被抑制的异常列表中。