57.只针对异常的情况才使用异常
简介
错误示例:
try{
int i = 0;
while(true){
rang[i++].climb();
}
}catch(ArrayIndexOutOfBoundsException e){}
- 异常机制的设计初衷是用于不正常的情形,所以很少会有
JVM
实现视图对他们进行优化 - 把代码放在
try-catch
块中反而阻止了现代 JVM本来可能要执行的某些优化. - 对数组进行遍历 的标准模式 并不会导致 冗余的检查.
而正确的写法,应该是:
for(Mountain m: range){
m.climb();
}
错误的观点:
认为
VM
对每次数组的访问都要检查越界情况,导致性能差.
使用异常模式
可以提高性能
事实
在现代 JVM
的实现中,基于异常的模式比标准模式 要慢得多,降低了性能.
基于异常模式会模糊代码意图,如果出现了bug
,它还会掩盖了 bug
.
小结
- 异常应该只用于异常情况下,不应该用于正常的控制流.
- 设计良好的API不应该强迫它的客户端为了正常的控制流而是用异常.
58.对可恢复的情况使用受检异常,对编程错误使用运行时异常
简介
Java 提供了三种可抛出结构: 受检异常,运行时异常和错误.
使用规范
- 如果期望调用者能够适当的恢复,就应该使用受检异常.
通过抛出受检异常,强迫调用者处理异常
- 运行时和错误都是不应该被捕获的可抛出结构
如果程序抛出这个,往往都是不可恢复的情形
- 用
运行时
异常表明编程错误
错误
往往是被JVM
保留用于标示资源不足
,约定失败
或者其他使程序无法继续执行的条件
小结
对于 可恢复
的情况,使用受检异常
对于程序错误
,使用运行时异常.
受检异常,往往需要指明一个可恢复的条件.
59.避免不必要的使用受检的异常
简介
上一个条目说 使用受检的异常,调用者就需要在
catch
中处理这个异常.
也就是说过分的使用受检异常
会使API 使用起来非常不方便,因为需要在一个或多个catch块
中处理这个异常. 或者thows
这些异常
小结
- 如果即便合理的调用了API也会遇到异常情形,并且捕获异常之后能够进行一些有意义的操作,才应该使用
checked exception
,其他情况下都应该使用RuntimeException
- 如果一个方法会抛出
checked exception
,都可以将其拆分为两个方法,一个用于判断是否会抛出异常,另一部分用于处理正常情况,如果不符合约定,就抛出RuntimeException
60.优先使用标准的异常
Java 平台类库未受检异常
代码重用是值得提倡的,异常也不例外
重用现有的异常有多方面的好处:
- 使你的
API
更加易于学习和使用 - 对于用到这些
API
的程序而言,可读性更好 - 异常类越少,意味着内存 印记 就越小,
装载这些异常
的时间开销
也越少
常用的有IllegalArgumentException
,IllegalStateException
…
小结
选择重用哪个异常,并不总是那么精确.没有严格的规则/.
61.抛出与抽象相对应的异常
简介
- 异常转译
更高层
的实现应该捕获低层
的异常,同时抛出
可以按照高层抽象进行解释
的异常. 就是异常转译
// Exception Translation
try {
// Use lower-level abstraction to do our bidding
...
} catch(LowerLevelException e) {
throw new HigherLevelException(...);
}
- 异常链
如果低层的异常对于调试导致高层异常的问题非常有帮助,就可以使用异常链
// Exception Chaining
try {
// Use lower-level abstraction to do our bidding
...
} catch(LowerLevelException e) {
throw new HigherLevelException(e);
}
大多数标准的异常 都支持
异常连的构造器
如果不支持,可以使用Throwable#initCause
来设置
异常连也不能乱用,最好的做法是, 在调用低层方法之前确保他们会成功执行,从而避免他们抛出异常
小结
- 如果
不能阻止
或者处理
来自更低层的异常
,一般的做法是使用异常转译
. - 如果碰巧
低层方法
可以保证它抛出的所有异常
对高层也合适
,就可以将异常
从低层传播到高层.
62.每个方法抛出的异常都要有文档
简介
- 不要通过声明抛出多个异常的父类来实现抛出多种异常的效果。
- 要利用
javadoc
的@throws
标记准确的记录下抛出受检异常的条件 - 不要使用
@throws
将未受检的异常也包含在方法的声明中 - 如果一个类的很多方法都抛出同一个异常,那么可以将文档放到
class doc
中,而不是method doc
中
63.在细节消息中包含能捕获失败的信息
简介
- 为了捕获失败,异常的细节信息,应该包含所有”对该异常有贡献”的参数和域的值.
- 异常类型的
toString
方法应该尽可能多地返回有关失败原因的信息. - 为异常的”失败捕获”信息提供一些访问方法是合适的.提供这样的方法对于受检异常,比未受检异常更为重要.
64.努力使失败保持原子性
简介
当对象抛出异常后,我们期望这个对象仍然保持在一种定义良好的可用状态.
- 一般而言,失败的方法调用,应该使对象保持在被调用之前的状态
具有这种属性的方法被称为具有失败原子性
创建具有失败原子性的方法
- 最 简单的方法 莫过于
设计一个不可变得对象
,如果对象是不可变的,那显然是具有原子性的 - 在可变对象上,最常见的方法是: 在
执行操作之前
,检查参数的有效性,
在对象的状态被改变之前抛出适当的异常,如:
public Object pop(){
if(size ==0){
throw new EmptyStackException();
//...
}
}
如果不在 开始进行检查,在执行过程中也会 抛出异常,然而会导致
size
域 保持在不一致的状态,导致后续调用都会失败
- 编写一段
恢复代码
,由它来拦截操作过程中发生的失败,以及使对象回滚到操作之前的状态. - 在对象的一份
临时拷贝上执行操作
,当操作完成之后再用临时拷贝中的内容替换对象中的内容
65.不要忽略异常
简介
这条看似是显而易见的.但是却常常被违反,因此不可以忽视它
设计者声明一个方法抛出异常,等于正在试图说明什么.
// Empty catch block ignores exception
try{}catch(Exception e){}
- 空的
catch
块,会使异常达不到应有的目的. - 至少,
catch
块也应该包含一条说明,说明为什么可以忽略这个异常.