受检查异常是Java语言的优秀特性。与返回代码不同,不再强迫程序员处理例外条件,从而极大地提高了可靠性。话说回来过度使用受检查异常会让使用API变得乏味。如果一个方法抛出了一个或多个受检查的异常,则调用该方法的代码必须用一个或多个catch子句来处理这些异常,或在方法声明中抛出这些异常,以向上传播。无论哪种方法,都会给程序员带来不少负担。
如果程序员正确使用了API也不能阻止例外条件发生,并且一旦发生这种异常条件时,使用API的程序员能采取一些有用的行为,哪么负出也就值得了。除非上述两种情形都满足,否则,未受检查的异常更为合适。作为试金石,可以自问一下程序员将如何处理这个异常,这是最好的做法吗?
} catch(TheCheckedException e) {
throw new AssertionError(); // Can't happen!
}
如果这样呢?
} catch(TheCheckedException e) {
e.printStackTrace(); // Oh well, we lose.
System.exit(1);
}
如果使用API的用户不能做得更好,则使用未受检查的异常就更合适。一个不能通过上面测试的例子是异常CloneNotSupportedException。这个异常会被Object.clone抛出,clone应该只在实现了Cloneable接口的对象上被调用(Item 11)。实际上,catch块中几乎总是有断言失败的特性。这个异常若被检查并不会给程序员带来益处,却需多费神,使程序更复杂。
如果方法只抛出一个受检查的异常,哪么由这个受检查异常带给程序员的负担就大大地加重了。如果还有其它异常,且方法要出现在try语句块中,则只要再增加catch块就能处理这些异常。如果方法只抛出一个异常,而就这一个方法又必须在一个try语句块。在这种情形下,有必要问一问是否就没有办法来避免使用受检查的异常?
将一个受检查的异常转换为一个未受检查的异常的技术是将抛出异常的方法一分为二,第一个方法返回一个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的这种重构结果实际上与Item 57的状态测试方法相同,所以适用相同的告诫:如果对象会被外部非同步方法并发访问,或因外部原因而改变状态,则这个重构就是不合适的,因为对象的状态可能会在方法actionPermitted与action 之间变化。如果actionPermitted方法必须重复action方法的工作,从性能方法考虑或许应取消这种重构。