effective-java-3rd-chinese/docs/notes/71. 避免不必要的使用受检异常.md
mr-dragon 68acbcf2e1 在每篇文章后面加2个换行
在每篇文章后面加2个换行,方便使用cat命令合并成一个文件的时候不会影响目录的生成
2019-06-14 13:56:01 +08:00

60 lines
4.5 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 71. 避免不必要的使用受检异常
  Java 程序员不喜欢受检异常,但是如果使用得当,它们可以改善 API 和程序。不返回码和未受检异常的是,它们强迫程序员处理异常的条件,大大增强了可靠性。也就是说,过分使用受检异常会使 API 使用起来非常不方便。如果方法抛出受检异常,调用该方法代码就必须在一个或者多个 catch 块中处理这些异常,或者它必须声明抛出这些异常,并让它们传播出去。无论使用哪一种方法,都给程序员增添了不可忽视的负担。这种负担在 Java 8 中更重了,因为抛出受检异常的方法不能直接在 Stream 中使用(详见第 45 条至第 48 条)。
  如果正确地使用 API 并不能阻止这种异常条件的产生,并且一旦产生异常,使用 API 的程序员可以立即采取有用的动作,这种负担就被认为是正当的。除非这两个条件都成立,否则更适合于使用未受检异常。作为一个石蕊测试(石蕊测试是指简单而具有决定性的测试),你可以试着问自己:程序员将如何处理该异常。下面的做法是最好的吗?
```java
} catch ( TheCheckedException e ) {
throw new AssertionError(); /* Can't happen! */
}
```
  下面这种做法又如何?
```java
} catch ( TheCheckedException e ) {
e.printStackTrace(); /* Oh well, we lose. */
System.exit( 1 );
}
```
  如果使用 API 的程序员无法做得比这更好那么未受检的异常可能更为合适。
  如果方法抛出的受检异常是唯一的,它给程序员带来的额外负担就会非常高。如果这个方法还有其他的受检异常,该方法被调用的时候,必须已经出现在一个 try 块中,所以这个异常只需要另外一个 catch 块。如果方法只抛出一个受检异常,单独这一个异常就表示:该方法必须放置于一个 try 块中,并且不能在 Stream 中直接使用。这种情况下,应该问问自己,是否还有别的途径可以避免使用受检异常。
  除受检异常最容易的方法是,返回所要的结果类型的一个 optional详见第 55 条)。这个方法不抛出受检异常,而只是返回一个零长度的 optional。这种方法的缺点是方法无法返回任何额外的信息来详细说明它无法执行你想要的计算。相反异常则具有描述性的类型并且能够导出方法以提供额外的信息详见第 70 条)。
  「把受检异常变成未受检异常」的一种方法是,把这个抛出异常的方法分成两个方法,其中第一个方法返回一个 boolean 值,表明是否应该抛出异常。这种 API 重构,把下面的调用序列:
```java
/* Invocation with checked exception */
try {
obj.action( args );
} catch ( TheCheckedException e ) {
... /* Handle exceptional condition */
}
```
  重构为:
```java
/* Invocation with state-testing method and unchecked exception */
if ( obj.actionPermitted( args ) ) {
obj.action( args );
} else {
... /* Handle exceptional condition */
}
```
  这种重构并非总是恰当的,但是,凡是在恰当的地方,它都会使 API 用起来更加舒服。虽然后者的调用序列没有前者漂亮,但是这样得到的 API 更加灵活。如果程序员知道调用将会成功,或者不介意由于调用失败而导致的线程终止,这种重构还允许以下这个更为简单的调用形式:
```java
obj.action(args);
```
  如果你怀疑这个简单的调用序列是否符合要求,这个 API 重构可能就是恰当的。这样重构之后的 API 在本质上等同于第 69 条中的「状态测试方法」,并且同样的告诫依然适用:如果对象将在缺少外部同步的情况下被并发访问,或者可被外界改变状态,这种重构就是不恰当的,因为在 actionPermitted 和 action 这两个调用的时间间隔之中,对象的状态有可能会发生变化。如果单独的 actionPermitted 方法必须重复 action 方法的工作,出于性能的考虑,这种 API 重构就不值得去做。
  总而言之,在谨慎使用的前提之下,受检异常可以提升程序的可读性;如果过度使用,将会使 API 使用起来非常痛苦。如果调用者无法恢复失败,就应该抛出未受检异常。如果可以恢复,并且想要迫使调用者处理异常的条件,首选应该返回一个 optional 值。当且仅当万一失败时,这些无法提供足够的信息,才应该抛出受检异常。