When catching exceptions, dont cast your net too wide
摘自JavaWorld,原文请看:When catching exceptions, dont cast your net too wide
By Dave Schweisguth
理解Java编译器在编译期是如何检查catch子句的。
摘要
与其它一些语言相比,Java的简单和一致性使编译器能检测许多错误。Java的开发者认识到如何依靠编译器对不正确的类型、不存在的方法的调用(这篇文章的主题)和不正确的异常处理进行捕获。但是在你真正需要知道你正在做什么的地方,那些你不想看到的情形仍会突然出现。如果你能确切的理解Java是怎样让你抛掷和捕获异常的,那么你就会知道你什么时候需要特别小心,什么样的习惯能让你远离烦恼。
Java的编译期检查对保持异常安全的框架进行了完美的支持,如果一个方法声明会抛掷一个异常,你无法在你的方法中不用捕获这个异常或声明你的方法也会抛掷这个异常的情况下调用那个方法。(更广泛的讨论请看“Designing with Exception”)编译器有时也会阻止你去捕获一个在try块中没有抛掷的异常,但并不总是如此,大多数时候都不会。这篇Java Tip又一次讨论了编译期检查。
throw子句的编译期检查
首先,让我们区别一下 Java 如何检查catch子句捕获的异常 与 Java如何检查一个方法中声明的会被抛掷出的异常。(在这篇文章中,当我用小写字母e开头来说exception时,那就指java.lang.Throwable和它的子类。当我想要指明一个明确的类时,像java.lang.Exception,我会包含包句或至少以大写字母开头的类名。)刚开始,这种方法看起来似乎很类似:两者都通过代码块的关联指明了预期被抛出的异常。但是,当Java要求一个方法声明它抛出的异常时,它并不是有所依托的要求那个方法抛出每一个声明的异常,Java允许你设计出在你添加了功能时程序保持稳定的API。
看一下下面这个自造的连接池的原始版本:
-
- public class ConnectionPool {
- public ConnectionPool() throws ConnectionException {
- }
-
- public Connection getConnection() throws ConnectionException {
- // Allocate a connection (possibly throwing a ConnectionException or a
- // subclass) if necessary, then return it
- }
- }
构造函数什么也不做,而在getConnection()方法中的代码可能会抛出一个ConnectionException的异常,所以在这种实现中,方法实际上不需要声明任何异常。但是在下一个版本中,我们重写了这个类以提高getConnection()的速度:
-
- public class ConnectionPool {
- public ConnectionPool() throws ConnectionException {
- // Allocate all the connections we think well ever need
- }
-
- public Connection getConnection() throws ConnectionException {
- // Allocate a connection if necessary (not likely), then return it
- }
- }
因为我们在第一个版本中写的构造函数声明了ConnectionException,所以使用它的代码不用为使用第二个版本而改写。Java对throw子句进行了一些检查,以便在所有其它调用这个构造函数的类中都有始终如一的稳定性--这是一种很好的方式。
catch子句的编译期检查
catch子句与throw子句是有不同的内容。API 稳定性的观点在这不适用:当一个方法声明是一个类的公共接口的一部分的时候,一个try/catch块就是一个从调用者的角度隐藏细节的实现。不但没有理解因为一个catch子句而去捕获一个try块没有抛掷的异常,而且保证它不会那么做以便能捕捉严重的代码错误。因为这个原因,Java要求你的try块确实抛出了一个它们的catch子句捕获的异常。例如,假设你对念念不忘以前的操作系统,并且写了下面一小段代码:
-
- public class rm {
- public static void main(String[] args) {
- for (int i = 0; i < args.length; i++) {
- try {
- new File(args[i]).delete();
- } catch (IOException e) { // Wont compile!!!
- System.err.println("rm: Couldnt delete " + args[i]);
- }
- }
- }
- }
Sun微系统公司的Java编译器将会告诉你IOException "is never thrown in body of corresponding try statement." 那是因为File.delete()根本没有抛出任何异常,而是在不能删除文件时返回false。如果不是编译期catch子句的异常检查,你可能会无意中写了一个失败的却没有明显错误的程序。
“但是,等等”,你是一个有思想且经验丰富的编码员,“人们一直在庞大的代码中捕获java.lang.Exception,并且在那些try块中并不总是抛出java.lang.Exception,但是仍然能编译通过!”你是对的。这里我们很快就达到我们的目标:究竟在对一个catch子句中允许捕获的异常进行检查时Java使用的规则是什么?
catch子句也会捕获子类
答案有两部分:第一是,一个catch子句捕获它的参数类型的异常和它的子类。这也是一个有价值的语言特性:一个声明抛出异常的方法,比如异常javax.naming.NamingException,那么这个方法确实能抛出许多任何NamingException的子类。方法的调用者需要知道为一个特殊子类能写一个catch子句;这样就不能仅仅捕获NamingException。更进一步,如果一个会抛出NamingException子类的方法的实现在后来的版本中改变了,那么原始的实现不需要改变,它的调用者也不需要改变。这种灵活性也赋予了API更大的稳定性。
事实上,catch子句捕获子类也会有一些小麻烦。例如,许多读者可能写过类似下面这样的实用方法:
-
- public class ConnectionUtil {
- /** Close the connection silently. Keep going even if theres a problem. */
补充:软件开发 , Java ,