ITEM 71: AVOID UNNECESSARY USE OF CHECKED EXCEPTIONS
许多Java程序员不喜欢检查异常,但如果使用得当,它们可以改进 API 和程序。与返回代码和未检查的异常不同,它们迫使程序员处理问题,提高了可靠性。与此同时,在 API 中过度使用受控异常会使它们使用起来非常不愉快。如果一个方法抛出检查过的异常,调用它的代码必须在一个或多个 catch 块中处理这些异常,或者声明它抛出这些异常并让它们向外传播。无论哪种方式,都会给 API 的用户带来负担。Java 8 中的负担增加了,因为抛出已检查异常的方法不能直接在流中使用(item 45-48)。
如果异常情况不能被 API 的正确使用所阻止,并且使用 API 的程序员在遇到异常时可以采取一些有用的操作,那么这种负担可能是合理的。除非这两个条件都满足,否则不检查异常是合适的。作为一个测试,问问你自己将如何处理异常。这是我们能做到的最好的结果吗?
} catch (TheCheckedException e) {
throw new AssertionError(); // Can't happen!
}
或者:
} catch (TheCheckedException e) {
e.printStackTrace(); // Oh well, we lose.
System.exit(1);
}
如果程序员不能做得更好,使用未检查的异常也许更好。
如果检查异常是方法抛出的唯一检查异常,则由检查异常给程序员造成的额外负担将大大增加。如果还有其他异常,则该方法必须已经出现在 try 块中,而这个异常最多需要另一个 catch 块。如果方法抛出单个检查异常,此异常是该方法必须出现在 try 块中而不能直接在流中使用的唯一原因。在这种情况下,最好问问自己是否有办法避免被检查的异常。
消除检查异常的最简单方法是返回所需结果类型的 Optional (item 55)。该方法不抛出检查异常,而只是返回一个空的结果。这种技术的缺点是,该方法不能返回任何说明其无法执行所需计算的额外信息。相反,异常具有描述性类型,并且可以导出方法来提供额外的信息(item 70)。
通过将抛出异常的方法分解为两个方法,还可以将检查异常转换为未检查异常,第一个方法返回一个布尔值,指示是否将抛出异常。这个API重构将调用序列由
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是声明测试方法 API (item 69)应用相同的警告:如果一个对象是被并发地访问没有外部同步或受外部诱导状态转换,这种重构是不恰当的,因为对象的状态可能会在调用 actionPermitted 和 action 之间改变。如果一个单独的 actionPermitted 会重复 action方法的工作,那么基于性能原因,重构可能会被排除。
总之,当有节制地使用时,检查异常可以提高程序的可靠性;当过度使用时,它们会使 API 使用起来很痛苦。如果调用方无法从失败中恢复,则抛出未检查的异常。如果可能进行恢复,并且希望强制调用者处理异常条件,那么首先考虑返回一个Optional。只有在失败的情况下,且需要提供足够的信息时,才应该抛出检查过的异常。
网友评论