《Effective Java》阅读笔记-第十章
Effective Java 阅读笔记
第十章 异常
第 69 条 只针对异常的情况才使用异常
说白了就是不要吧你的业务逻辑用异常来写。
举个反例
比如用抛出异常来遍历一个数组:
try {
int i = 0;
while(true) {
range[i++].doSomething();
}
} catch (ArrayIndexOutOfBoundsException e) {
}
这种代码我只想说,那你可真是可小天才。
通常来说:
- 异常应该只适用于出现异常的情况下,它永远不应该用于正常的控制流。
- 设计良好的 API 不应该强迫调用者为了正常的控制流而使用异常。
比如Iterator<T>
接口,这个接口有一个next()
取值方法和hasNext()
检测方法,这样就可以使用传统 for 循环(以及 for-each,因为内部使用了 hasNext)的调用成为可能:
Collection<String> collection = new ArrayList<>();
for (Iterator<String> iter = collection.iterator(); iter.hasNext(); ) {
String next = iter.next();
}
否则只能使用异常去进行流程控制:
Iterator<String> iterator = collection.iterator();
try {
while (true) {
String next = iterator.next();
}
} catch (NoSuchElementException e) {
}
这一点深有同感,特别是在对比 C# 和 Java 的之后,Java 中在进行类型转换的时候,比如字符日转换成 Integer 或者 Double 之类的,没有 TryParse 方法,C# 就提供了这样的方法。
第 70 条 对可恢复的错误使用受检异常,对编程错误使用运行时异常
Java 中提供了三种可抛出结构(Throwable):受检异常(checked exception)、运行时异常(runtime exception)、错误(error)。
一般来说,如果期望调用者能够适当地回复(recover),那么就用受检异常。而运行时异常通常来表名编程错误,比如访问了业界的下标,就会抛出数组越界异常。
同时,自己实现的所有未受检异常都应该是RuntimeException
的子类,不应该定义或者抛出 Error 的子类,Error 一般是虚拟机无法处理的时候就会抛出 Error。
第 71 条 避免不必要地使用受检异常
收件异常强迫调用者去处理异常情况,这会使 API 非常难以使用。
当且仅当万一失败,并且还无法提供足够的信息时,才抛出受检异常。
第 72 条 优先使用标准的异常
标准库中定义了许多较为通用的异常,使用这些异常会使 API 更容易学习和使用,并且可读性也会更好。
但是不要直接重用 Exception、RuntimeException、Error。
这是一些常用异常:
异常 | 使用场合 |
---|---|
IllegalArgumentException | 非 null 的参数值不正确 |
IllegalStateException | 不合适的调用状态 |
NullPointerException | 禁止使用 null 的情况下参数为 null |
IndexOutOfBoundsException | 下标参数值越界 |
ConcurrentModificationException | 禁止并发修改的情况下检测到了并发修改 |
UnsupportedOperationException | 对象不支持用户请求的方法 |
一般来说,不要自己编写异常类。
第 73 条 抛出与抽象对应的异常
如果抛出的异常和它执行的任务没有直接关系,会让人感到困惑、不知所措。
因此更高层的实现应该捕获底层的异常,同时可以抛出按照高层抽象进行解释的异常,这叫异常转译(exception translation)。
比如:
// 异常转译(exception translation)
try {
// 内部处理抛出了低等级异常
} catch (LowerLevelException e) {
throw new HigherLevelException(...);
}
尽管转译相对直接抛出低级异常有优势,但是也不能滥用。
第 74 条 每个方法抛出的所有异常都要建立文档
文档中始终要单独声明受检异常,并记录下每个异常的抛出条件。
使用 Javadoc 的@throws
标签记录方法可能抛出的所有异常,不要在方法签名上写未受检异常。
第 75 条 将失败捕获信息添加到异常详细信息中
主动抛出异常时,异常中除了包含堆栈信息,还可以添加自定义的消息,方便查找问题。
第 76 条 失败应保持原子性
一般而言,方法调用失败时应该使对象保持在被调用之前的状态,这叫原子性失败(failure atomic)。
第 76 条 不要忽略异常
空的 catch 块会使异常不能达到应有目的,也就是强迫你进行处理。
如果选择忽略异常,catch 块中应包含一条注释,说明为什么可以这样做,并将异常变量命名为 ignored:
Future<Integer> f = exec.submit(planarMap::chromaticNumber)
int numColors = 4; // 默认值
try {
numColors = f.get(1L, TimeUnit.SECONDS);
} catch (TimeoutException | ExecutionException ignored) {
// 使用默认值,颜色设置是可选的,非必须的。
}