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) {
    // 使用默认值,颜色设置是可选的,非必须的。
}

标签: Java

添加新评论