Tips
书中的源代码地址:
注意, 书中的有些代码里方法是基于 Java 9 API 中的, 所以 JDK 最好下载 JDK 9 以上的版本.
71. 避免不必要地使用检查异常
许多 Java 程序员不喜欢检查异常, 但如果使用得当, 他们可以改进 API 和程序. 与返回码和未检查异常不同, 它们迫使程序员处理异常问题, 增强可靠性. 也就是说, 在 API 中过度使用检查异常会使它们使用起来不那么令人愉快. 如果方法抛出检查异常, 则调用它的代码必须在一个或多个 catch 块中处理它们, 或者声明抛出它们并向上传播. 无论哪种方式, 它都会给 API 的使用者带来负担. 这种负担在 Java 8 中加重了, 因为抛出检查异常的方法不能直接在 Stream 中使用 (条目 45--48).
如果不能通过正确使用 API 来防止异常情况, 并且使用 API 的程序员在遇到异常时可以采取一些有用的操作, 那么这种负担是合理的. 除非满足这两个条件, 否则可以使用未检查异常. 作为最后的检验 (litmus test), 可以问问自己: 程序员将如何处理异常. 这是最好的办法吗?
- } catch (TheCheckedException e) {
- throw new AssertionError(); // Can't happen!
- }
或者这样:
- } catch (TheCheckedException e) {
- e.printStackTrace(); // Oh well, we lose.
- System.exit(1);
- }
如果程序员不能做得更好, 则需要一个未检查异常.
如果方法抛出的检查异常是惟一的, 那么检查异常给程序员带来的额外负担就会大得多. 如果还有其他方法, 则该方法必须已经出现在 try 块中, 并且最多需要另一个 catch 块. 如果一个方法抛出单个检查异常, 那么这个异常就是该方法必须出现在 try 块中, 并且不能直接在 Stream 中使用. 在这种情况下, 有必要问问自己是否有办法避免检查异常.
消除检查异的最简单方法是返回所需结果类型的 Optional(条目 55). 该方法只返回一个空的 Optional, 而不是抛出一个检查的异常. 这种方法的缺点是, 该方法不能返回任何详细说明其无法执行所需计算的额外信息. 相反, 异常具有描述性类型, 并且可以导出方法来提供额外的信息 (条目 70).
还可以通过将抛出异常的方法分解为两个方法, 将检查异常转换为未检查异常, 第一个方法返回一个 boolean 值, 表示是否抛出异常. 这个 API 重构将调用序列:
- // Invocation with checked exception
- try {
- obj.action(args);
- } catch (TheCheckedException e) {
- ... // Handle exceptional condition
- }
转换为:
- // Invocation with state-testing method and unchecked exception
- if (obj.actionPermitted(args)) {
- obj.action(args);
- } else {
- ... // Handle exceptional condition
- }
这种重构并不总是合适的, 但是它可以使 API 更加舒适. 虽然后者调用序列并不比前者更漂亮, 但重构的 API 更灵活. 如果程序员知道调用将成功, 或者满足于让线程在失败时终止, 那么重构也允许这个简单的调用序列:
obj.action(args);
如果怀疑普通的调用序列会成为常态, 那么 API 重构可能是合适的. 生成的 API 本质上与条目 69 中的状态测试方法 API, 并且适用与相同的警告: 如果要在没有外部同步的情况下同时访问对象, 或者被外部转换状态, 则此重构是不合适的, 因为对象的状态可能是在对 actionPermitted 和 action 的调用之间进行更改. 如果单独的 actionPermitted 方法会重复 action 方法的工作, 则可能会因性能原因而排除重构.
总之, 如果谨慎使用, 检查异常可以提高程序的可靠性; 当过度使用, 会使 API 难以使用. 如果调用者无法从失败中恢复, 则抛出未检查异常. 如果恢复是可能的, 并且希望强制调用者处理异常条件, 那么首先考虑返回 Optional 的. 只有当在失败的情况下, 无法提供充分的信息时, 才应该抛出一个检查的异常.
来源: https://www.cnblogs.com/IcanFixIt/p/10619408.html