第9章异常
充分发挥异常的优点,可以提高程序的可读性、可靠性和可维护性。
57.只针对异常的情况才使用异常
异常只用于异常的情况,永远不能用于正常的流程控制。
设计良好的 api 不应该强迫它的客户端为了正常的控制流使用异常。
具体应用方法:如果类具有“状态相关”的方法,即只有在特定的不可预知的条件下才可以被调用的方法,这个类往往也应该有个单独的“状态测试”方法,即指示是否可以调用这个状态相关的测试方法。例如,Iterator接口有一个“状态相关”的next方法,和相应的状态测试方法hasNext。这使得利用传统的for循环(或者foreach循环)对集合进行迭代的标准模式成为可能:
For(Iterator<Fool> i=collection.iterator;i.hasNext();){
Fool fool=i.next();
...
}
另一种提供单独的状态测试的做法是,如果“状态相关的”方法被调用时,该对象处于不适当的状态中,它就会返回一个可识别的值,如null。
58. 对可恢复的情况使用受检异常,对编程错误使用运行时异常
Java提供了三种可抛出结构:受检的异常,运行时异常和错误。
如果期望调用者能够适当地恢复,对于这种情况就应该使用受检的异常。
用运行时异常来表明编程错误。大多数的运行时异常都表示前提违例,前提违例指API的客户没有遵守API规范建立的约定。例如,数组访问的约定指明了数组的下标值必须是在0和数组长度减1之间。
错误往往被JVM保留用于表示资源不足、约束失败,或者其他使程序无法继续执行的条件。
59.避免不必要地使用受检的异常
受检的异常强迫程序员处理异常的条件,大大增强了可靠性。
如果正确的使用API并不能阻止这种异常条件的产生,并且一旦产生异常,使用API的程序员可以立即采取有用的动作,这种负担就被认为是正当的。除非以上两条规则都成立,否则更适合于使用非受检的异常。
把受检的异常编变成未受检的异常的一种方法是,把这个抛出异常的方法分成两个方法,其中第一个方法返回一个boolean,表明是否该抛出一个异常。具体代码如下:
try{
Obj.action(args);
}catch(TheCheckedException e){
//handle exceptional condition
}
重构后:
if(obj.actionPermitted(args)){
Object.action(args);
}else{
//handle exceptional condition
}
这种重构不适合在缺少外部同步的情况下被并发访问或者可被外界改变状态的情景。
60.优先使用标准的异常
重用现有异常的好处:
- 使API更加易于学习和使用。
- 代码可读性会更好。
- 异常类减少,装载这些类的时间开销变少。
常用的异常:
61.抛出与抽象相对应的异常
更高层的实现应该捕获顶层的异常,同时抛出可以按照高层抽象进行解释的异常,这种方式被称为异常转译 。
例如,AbstractSequentialList的get方法抛出的异常:
如果低层的异常对于调试导致高层的异常问题很有帮助,可以将它传到高层的异常,高层的异常提供访问方法来获得低层的异常。这种特殊转译形式被称为异常链。
代码结构如下:
try{
}catch(LowerLevelException cause){
throw new HigherLevelException(cause);
}
62.每个方法抛出的异常都要有文档
始终单独的声明受检异常,并用@throw 标记,准确记录下抛出每个异常的条件。
永远不要声明方法 throws Exception 或者 throws Throwable 。
不要将未受检的异常包含在方法声明中 。
如果一个类中许多方法出于同样的原因而抛出同一个异常,在该类的文档注释中对这个异常建立文档,是可以接受的,而不是为每个方法建立文档。
63.在细节消息中包含能捕获失败的信息
异常类型的toString方法应该尽可能多地返回有关失败原因的信息。
为了捕获失败,异常信息应该包含所有对该异常有贡献的参数和域的值,例如 IndexOutOfBoundsException应当包含 上界,下界以及没有落在界内的下标值
异常的细节消息不应该与用户层次的错误消息混为一谈
为了确保异常的细节消息包含足够信息,可以在异常构造器而不是字符串细节消息中引入这些信息。
64.努力使失败保持原子性
失败的方法调用应该使对象保持在被调用之前的状态。具有这种属性的方法被称为失败原子性。
实现失败原子性的几种方法:
- 设计一个不可变对象。
- 可变对象上,在执行操作前检查参数的有效性。
- 编写一段恢复代码,由它来拦截操作过程中发生的失败,以及使对象回滚到操作开始之前的状态上。这种方法主要用于永久性(基于磁盘)的数据结构。
- 在对象的一份临时拷贝上操作。例如,Collections.sort在执行排序之前,首先把它的输入列表转到一个数组中,以便降低在排序的内循环中访问元素所需要的开销。这是出于性能考虑的做法,但它增加了一项优势:即使排序是白,它也能保证输入列表保持原样。
具体代码如下:
65.不要忽略异常
catch 块中至少包含说明,解释为何可以忽略异常
参考资料:
https://blog.csdn.net/jslcylcy/article/details/70558907
https://blog.csdn.net/lyl_tkb/article/details/37600037
《Effective java》