Java中异常的类体系结构如下:
java.lang.Object
java.lang.Throwable
java.lang.Exception
java.lang.RuntimeException
Throwable类是java语言中所有错误和异常的超类,它拥有两个子类:
(1)Error:用于指示合理的应用程序不应该试图捕获的严重问题,例如java虚拟的错误程序无法处理,只能由java虚拟机自己处理。
(2)Exception:用于指示合理的应用程序应该捕获的条件。
Exception又分为:
(1)Checked Exception即受检异常:调用时必须显式使用try-catch捕获,或者在方法上使用throws显式向外抛出。
常用的受检异常有:ClassNotFoundException,InterruptedException,IOException,NoSuchMethodException,SAXException等等。
(2)RuntimeException运行时异常(Unchecked Exception非受检异常):可以不用显式使用try-catch捕获或者在方法上使用throws显式向外抛出。
常用的运行时异常有:ArithmeticException,ClassCastException,IllegalArgumentException,NullPointerException,IndexOutOfBoundException等等。
异常使用指导原则:
C++/C#中只有运行时异常,没有类似java的受检异常,很多资深人士也建议在自定义异常时尽量使用运行时异常,因为方法中抛出异常变化时客户端不受影响,Effective java中给出一般性的指定原则如下:
(1)如果期望调用者能够是当地恢复,应该使用受检异常。
通过抛出受检异常,强迫调用者在一个catch子句中处理该异常,或者将它传播出去,受检异常对使用者进行一种潜在指示:与异常相关的条件是调用方法的一种可能的结果,以此强制用户从异常条件中恢复。
受检异常与返回代码不同,它们强制程序员处理异常的条件,大大增强了可靠性。但是过分使用受检异常会使API使用起来非常不方便,如果方法抛出一个或者多个受检异常,调用该方法的代码就必须在一个或者多个catch块中处理这些异常,或者调用者必须显式声明向外抛出这些异常,无论是在catch块中处理或者是向外抛出,都给程序员带来额外的负担。
如果正确地使用API并不能阻止这种异常条件的产生,并且一旦产生异常,使用API的程序员可以立即采取有效的动作,则就可以使用受检异常。
(2)使用运行时异常来表明编程错误。
大多数的运行时异常都是表示前置条件错误,即使用者没有遵守调用的约定,例如数组越界等。
如果正确地使用API可以避免异常条件的产生,或者一旦产生异常使用API的程序员无法立即采取有效措施,则应该使用运行时异常。
优先使用标准的异常:
面向对象编程中非常推崇代码重用,异常也不例外,JDK提供了一组基本的运行时异常可以满足绝大数API的异常抛出需求,重用JDK现有的异常有以下好处:
(1)使API更加容易学习和使用
(2)对于使用API的程序而言,异常的可读性会更好
(3)异常类越少,内存印迹就越小,装载异常类的时间和空间开销也越少。
恰当地重新抛出异常:
异常处理常用的策略:
(1)调用者完全可以处理的异常:在catch块中捕获后进行相应的处理
(2)调用者无法处理或者不应该由该层调用处理的异常:显式throws声明抛出,或者在catch块中捕获以后转换成高层容易理解的异常抛出。
对于包装转换的异常有以下两种处理方式:
(1)异常转译
将catch块中捕获的底层异常按照高层抽象进行解释包装,例如集合容器按索引下标获取元素的方法:
public E get(int index){
ListIterator<E> i = listIterator(index);
try{
return i.next();
}catch(NoSuchElementException e){
throw new IndexOutOfBoundException("Index:" + index);
}
}
高层调用者不了解集合容器的底层实现细节,NoSuchElementException异常对于调用者来说可能不够直观,而IndexOutOfBoundsException对于使用这就很明确地知道是数组下标越界。
但是异常转译的一个缺点是在重新抛出高层异常的同时丢失了底层异常信息,因此需要使用异常链来避免异常信息丢失。
(2)异常链
如果底层的异常对于调试导致高层异常的问题非常有帮助,则将底层异常的原因传递到高层的异常中,高层的异常通过Throwable提供的getCause方法来获得底层的异常信息,这就是异常链,例子如下:
try{
....
}catch(LowerLevelException cause){
throw new HigherLevelException(cause);
}
高层异常实现时需要添加支持异常链的构造器,例子如下:
class HigherLevelException extends Exception{
HigerLevelException(Throwable cause){
super(cause);
}
}
大多数标准的异常都有支持异常链的构造器,对于没有支持链的异常,可以利用Throwable的initCause方法设置原因,异常链不仅可以通过程序使用getCause方法获取异常原因,还可以将底层异常的异常栈集成到更高层的异常中,以方便对异常原因进行分析。