一、异常匹配
抛出异常的时候,异常处理系统会按照代码的书写顺序找出“最近”的处理程序。找到匹配的处理程序之后,它就认为异常将得到处理,然后就不再继续查找。
查找的时候并不要求抛出的异常同处理程序所声明的异常完全匹配。派生类的对象也可以匹配其基类的处理程序,就像这样:
class Annoyance extends Exception {
}
class Sneeze extends Annoyance {
}
public class Human {
public static void main(String[] args) {
try {
throw new Sneeze();
} catch (Sneeze e) {
System.out.println("Caught Sneeze");
} catch (Annoyance e) {
System.out.println("Caught Annoyance");
}
try {
throw new Sneeze();
} catch (Annoyance e) {
System.out.println("Caught Annoyance");
}
}
}
Sneeze异常会被第一个匹配的catch子句捕获,也就是程序里的第一个。然而如果将这个catch子句删掉,只留下Annoyance的catch子句,该程序仍然能运行,因为这次捕获的是Sneeze的基类。换句话说,catch(Annoyance e)会捕获Annoyance以及所有从它派生的异常。这一点非常有用,因为如果决定在方法里加上更多派生异常的话,只要客户程序员捕获的是基类异常,那么它们的代码就无需更改。
如果把捕获基类的catch子句放在最前面,以此想把派生类的异常全给“屏蔽”掉,就像这样:
try {
throw new Sneeze();
} catch (Annoyance e) {
System.out.println("Caught Annoyance");
} catch (Sneeze e) {
System.out.println("Caught Sneeze");
}
这样编译器就会发现Sneeze的catch子句永远也得不到执行,因此它会向你报告错误。
二、其他杂谈
异常处理系统就像一个活门(trap door),使你能放弃程序的正常执行序列。当“异常情形”发生的时候,正常的执行已变得不可能或者不需要了,这时就要用到这个“活门”。异常代表了当前方法不能继续执行的情形。开发异常处理系统的原因是,如果为每个方法所有可能发生的错误都进行处理的话,任务就显得过于繁重了,程序员也不愿意这么做。结果常常是将错误忽略。应该注意到,开发异常处理的初衷是为了方便程序员处理错误。
异常处理的一个重要原则是“只有在你知道如何处理的情况下才捕获异常”。实际上,异常的处理的一个重要目标就是把错误处理的代码同错误发生的地点相分离。这使你能在一段代码中专注于要完成的事情,至于如何处理错误,则放在另一段代码代码中完成。这样一来,主干代码就不会与错误处理逻辑混在一起,也更容易理解和维护。通过允许一个处理程序去处理多个出错点,异常处理还使得错误处理代码的数量趋向于减少。
“被检查的异常”使这个问题变得有些复杂,因为它们强制你在可能还没准备好处理错误的时候被迫加上catch子句,这就导致了吞食则有害(harmful if swallowed)的问题:
try {
// ...to do something useful
} catch (ObligatoryException e) {
}
程序员们只做最简单的事情,常常是无意中“吞食”了异常;然而一旦这么做,虽然能通过编译,但除非你记得复查并改正代码,否则异常将会消失。异常确实发生了,但“吞食”后它却完全消失了。因为编译器强迫你立刻写代码来处理异常,所以这种看起来最简单的方法,却可能是最糟糕的做法。
对于简单的程序,最简单而又不用写多少代码就能保护异常信息的方法,就是把它们从main()传递到控制台。例如,为了读取信息而打开一个文件,必须对FileInputStream进行打开和关闭操作,这就可能会产生异常。对于简单的程序,可以像这样做:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class MainException {
public static void main(String[] args) throws Exception {
FileInputStream file = new FileInputStream("MainException.java");
file.close();
}
}
注意,main()作为一个方法也可以有异常声明,这里异常的类型是Exception,它也是所有“被检查的异常”的基类。通过把它传递到控制台,就不必在main()里写try-catch子句了。
在编写你自己使用的简单程序时,从main()中抛出异常是很方便的,但这不是通用的方法。问题的实质是,当在一个普通方法里调用别的方法时,要考虑到“我不知道该怎样处理这个异常,但是也不想把它‘吞’了,或者打印一些无用的消息”。jdk 1.4的异常链提供了一种新的思路来解决这个问题。可以直接把“被检查异常”包装进RuntimeException里面,就像这样:
try{
// to do something useful
} catch (IDontKnowWhatToDoWithThisCheckedException e) {
throw new RuntimeException(e);
}
如果想把“被检查异常”这种功能“屏蔽”掉的话,这看上去像是一个好办法。不用“吞下”异常,也不必把它放到方法的异常说明里面,而异常链还能保证你不会丢失任何原始异常的信息。
这种技巧给了你一种选择,你可以不写try-catch子句和/或异常说明,直接忽略异常,让它沿着调用栈往上“冒泡”。同时,还可以用getCause()捕获并处理特定的异常,就像这样:
import java.io.FileNotFoundException;
import java.io.IOException;
class WrapCheckedException {
void throwRuntimeException(int type) {
try {
switch (type) {
case 0:
throw new FileNotFoundException();
case 1:
throw new IOException();
case 2:
throw new RuntimeException("Where am I?");
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
class SomeOtherException extends Exception {
}
public class TurnOffChecking {
public static void main(String[] args) {
WrapCheckedException wce = new WrapCheckedException();
wce.throwRuntimeException(3);
for (int i = 0; i < 4; i++) {
try {
if (i < 3)
wce.throwRuntimeException(i);
else
throw new SomeOtherException();
} catch (SomeOtherException e) {
System.out.println("SomeOtherException: " + e);
} catch (RuntimeException re) {
try {
throw re.getCause();
} catch (FileNotFoundException e) {
System.out.println("FileNotFoundException: " + e);
} catch (IOException e) {
System.out.println("IOException: " + e);
} catch (Throwable e) {
System.out.println("Throwable: " + e);
}
}
}
}
}
WrapCheckedException.throwRuntimeException()的代码可以生成不同类型的异常。这些异常被捕获并包装进了RuntimeException对象,所以它们成了这些运行时异常的“cause”了。
在TurnOffChecking里,可以不用try块就调用throw RuntimeException()因为它没有抛出“被检查异常”。但是,当你准备好去捕获异常的时候,还是可以用try块来捕获任何你想捕获的异常。应该捕获try块肯定会抛出的异常,这里就是SomeOtherException。RuntimeException要放到最后去捕获。然后把getCause()的结果(也就是被包装的那个原始异常)抛出来。这样就把原先的那个异常给提取出来了,然后就可以用它们自己的catch子句进行处理。
如果本文对您有很大的帮助,还请点赞关注一下。