Effective Java 2 Exception
第57条:只针对异常的情况才使用异常
异常设计用于不正常的情形,现代JVM会很少对其优化,使得运行较慢。
异常应该只用于异常的情况下,它们永远不应该用于正常的控制流。
设计良好的API不应该强迫它的客户端为了正常的控制流而使用异常。
如果类具有状态相关的方法(Iterator的next方法),就应该提供相应的状态测试方法(hasNext),来避免正常的迭代抛出异常
第58条:对可恢复的情况使用受检异常,对编程错误使用运行时异常
Java提供了3中可抛出的结构:受检的异常、运行时异常、错误。
主要原则:
- 如果期望调用者能够适当地恢复,应该使用受检的异常。通过抛出该异常,让调用者在一个catch中处理,或者将他传播出去。
- 运行时异常和错误,它们都是不需要也不应该捕获的可抛出结构。如果抛出,则是不可以恢复的情形。
第59条:避免不必要地使用受检的异常
原因:过分使用受检的异常会使API使用起来非常不方便。如果方法抛出一个或多个受检的异常,则调用者必须在catch中处理它或者将它们抛出。
解决方法之一:将抛出异常的方法分成2个方法,其中一个方法返回一个boolean,表明是否应该抛出异常。如下:
|
|
当然这种重构在一些情况并不是恰当的。如果对象在缺少同步情况下,在actionPermitted和action之间对象的状态可能发生了变化,就不值得这么做了。
第60条:优先使用标准的异常
重用现有的异常。如下常见的可重用异常:
- IllegalArgumentException 非null值的参数值不正确
- IllegalStateException 对于方法调用而言,对象状态不合适
- NullPointerException 在禁止使用null的情况下参数值为null
- IndexOutOfBoundsException 下标参数值越界
- ConcurrentModificationException 在禁止并发修改的情况下,检测到对象的并发修改
- UnsupportedOperationException 对象不支持用户请求的方法
第61条:抛出与抽象相对应的异常
如果方法抛出的异常与它所执行的任务没有明显的联系,会使人不知所措。
解决:更高层的实现应该捕获低层的异常,同时抛出可以按照高层抽象进行解释的异常, 这种做法称作异常转译
。如下:
|
|
异常转译的一种特殊方法:异常链
如果低层的异常对调试导致高层异常的问题非常有帮助,可以使用异常链,将低层的异常传到高层的异常。如下:
|
|
但是,最好的做法是。在调用低层方法之前,确保它们会被成功的执行,从而避免它们抛出异常。 同时,对于无法避免的异常,应该通过log记录下来。
总而言之,如果不能阻止或者处理来自更低层的异常,一般的做法是使用异常转译。
第62条:每个方法抛出的异常都要有文档
利用Javadoc的@throws标记,记录下抛出每个受检异常的条件。 要为每个受检异常提供单独的throws子句,不要为未受检的异常提供throws子句。
第63条:在细节消息中包含能捕获失败的信息
为了捕获失败,异常的细节信息应该包含所有"对该异常有贡献"的参数和域的值。
第64条:努力使失败保持原子性
当对象抛出异常之后,通常我们期望这个对象仍然保持在一种定义良好的可用状态之中,以期望能从异常中恢复。 一般而言,失败的调用应该使对象保持在被调用之前的状态,称作失败的原子性。方法:
- 在执行操作之前检查参数的有效性
- 调整计算处理过程的顺序,使得任何可能会失败的计算部分都在对象状态被修改之前发生。如TreeMap,在企图添加不正确元素,在tree被修改之前,自然会导致ClassCastException
- 编写恢复代码,由它来拦截操作过程中发生的失败,以及使对象回滚到操作开始之前的状态上。
- 在对象的一份临时拷贝上执行操作,当操作完成之后,再将临时拷贝中的结果代替对象的内容。如Collections.sort在执行之前,会把输入转到一个数组中,这样当排序失败,它也能保证输入保持原样。
一般而言,产生任何异常都应该让对象保持在该方法调用之前的状态。否则,API就应该指明对象会处在什么状态。
第65条:不要忽略异常
当API抛出一个异常时,说明会发生某些事情。所以请不要使用空的catch块。