声明受查异常
如果遇到了无法处理的情况,那么Java的方法可以抛出一个异常。这个道理很简单:一个方法不仅需要告诉编译器将要返回什么值,还要告诉编译器有可能发生什么错误。例如:一段读取文件的代码知道有可能读取的文件不存在,或者内容为空,因此,试图处理文件信息的代码就需要通知编译器可能会抛出IOException类的异常。
方法应该在其首部声明所有可能抛出的异常。这样可以从首部反映出这个方法可能抛出哪类受查异常。例如,下面是标准类库中提供的FileInputStream类的一个构造器的声明。
public FileInputStream(String name) throws FileNotFoundException
这个声明表示这个构造器将根据给定的String参数产生一个FileInputStream对象,但也有可能抛出一个FileNotFoundException异常。如果发生了这种糟糕的情况,构造器将不会初始化一个新的FileInputStream对象,而是抛出一个FileNotFoundException类对象。如果这个方法真的抛出了这样一个异常对象,运行时系统就会开始搜索异常处理器,以便知道如何处理FileNotFoundException对象。
在自己编写方法时,不必将所有可能抛出的异常都进行声明。至于什么时候需要在方法中用throws子句声明异常,什么异常必须使用throws子句声明,需要记住在遇到下面4中情况时应该抛出异常:
- 调用一个抛出受查异常的对象,例如,FileInputStream构造器。
- 程序运行过程中发现错误,并且利用throw语句抛出一个受查异常。
- 程序出现错误,例如,a[-1] = 0会抛出一个ArrayIndexOutOfBoundsException这样的非受查异常。
- Java虚拟机和运行时库出现的内部错误。
如果出现前两种情况之一,则必须告诉调用这个方法的程序员有可能抛出异常。为什么?因为任何一个抛出异常的方法都有可能是一个死亡陷阱。如果没有处理器捕获这个异常,当前执行的线程就会结束。
对于那些可能被他人使用的Java方法,应该根据异常规范(exception specification),在方法的首部声明这个方法可能抛出的异常。
class MyAnimation {
public Image loadImage(String s) throws IOException {
...
}
}
如果一个方法有可能抛出多个受查异常类型,那么就必须在方法的首部列出所有的异常类。每个异常类之间用逗号隔开。如:
class MyAnimation {
public Image loadImage(String s) throws FileNotFoundException, EOFException {
...
}
}
但是,不需要声明Java的内部错误,即从Error继承的错误。任何程序代码都具有抛出那些异常的潜能,而我们对其没有任何控制能力。
同样,也不应该声明从RuntimeException继承的那些非受查异常。如:
class MyAnimation {
void drawImage(int i) throws ArrayIndexOutOfBoundsException { // 这样是不对的
...
}
}
这些运行时错误完全在我们的控制之下。如果特别关注数组下标引发的错误,就应该将更多的时间花费在修正程序中的错误上,而不是说明这些错误发生的可能性上。
总之,一个方法必须声明所有可能抛出的受查异常,而非受查异常要么不可控制(Error),要么就应该避免发生(RuntimeException)。如果方法没有声明所有可能发生的受查异常,编译器就会发出一个错误消息。
当然,从前面的示例中可以知道:除了声明异常之外,还可以捕获异常。这样会使异常不被抛到方法之外,也不需要throws规范。稍后,将会讨论如何决定一个异常是被捕获,还是被抛出让其他的处理器进行处理。
如果类中的一个方法声明将会抛出一个异常,而这个异常是某个特定类的实例时,则这个方法将有可能抛出一个这个类的异常,或者这个类的任意一个子类的异常。例如,FileInputStream构造器声明将有可能抛出一个IOException异常,然而并不知道具体是哪种IOException异常。它既可能是IOException异常,也坑你是其子类的异常。如:FileNotFoundException。
抛出异常
假设在程序代码中发生了一些很糟糕的事情。一个名为readData的方法正在读取一个首部具有下列信息的文件:
Content-length: 1024
然而,读到733个字符之后文件就结束了。我们认为这是一种不正常的情况,希望抛出一个异常。
首先要决定应该抛出什么类型的异常。将上述异常归结为IOException是一种很好的选择。仔细地阅读Java API文档之后会发现:EOFException异常描述的是“在输入过程中,遇到了一个未预期的EOF后的信号”。这正是我们要抛出的异常。下面是抛出这个异常的语句:
throw new EOFException();
或者
EOFException e = new EOFException();
throw e;
和代码放在一起:
String readData(Scanner in) throws EOFException {
while (...) {
if (!in.hasNext()) {
if (n < len)
throw new EOFException();
}
}
}
EOFException 类还有一个含有一个字符串型参数的构造器。这个构造器可以更加细致的描述异常出现的情况。
String gripe = "Content-length:" + len + ", Receive:" + n;
throw new EOFException(gripe);
在前面已经看到,对于一个已经存在的异常类,将其抛出非常容易。
- 找到一个合适的异常类。
- 创建这个类的一个对象。
- 将对象抛出。
一旦方法抛出了这个异常,这个方法就不可能返回到调用者。也就是说,不必为返回的默认值或错误代码担忧。
创建异常类
在程序中,可能会遇到任何标准异常类都没有能够充分地描述清楚的问题。在这种情况下,创建自己的异常类就是一件顺利成章的事情了。我们需要做的只是定义一个派生于Exception的类,或者派生于Exception子类的类。例如,定义一个派生于IOException的类。习惯上,定义的类应该包含两个构造器,一个是默认的构造器;另一个是带有详细描述信息的构造器(超类Throwable的toString方法将会打印出这些详细信息,这在调试中非常有用)。
class FileFormatException extends IOException {
public FileFormetExcetpion() {};
public FileFormatExcetpin(String gripe) {
super(gripe);
}
}
现在,就可以抛出自己定义的异常类型了。
String readData(BufferedReader in) throws FileFormatException {
while (...) {
if (ch == -1) {
if (n < len)
throw new FileFormatException();
}
}
}
捐赠
若你感觉读到这篇文章对你有启发,能引起你的思考。请不要吝啬你的钱包,你的任何打赏或者捐赠都是对我莫大的鼓励。