若一个用户在程序运行期间,由于程序bug或者外部环境变动导致异常,应做到如下功能:
- 向用户报告错误
- 保存所有的工作结果。
- 允许用户以妥善形式退出程序。
对于异常情况,Java使用一种称之为异常处理Exception handing的错误捕获机制处理。这点和C++的异常处理机制十分相似。
由于出现了错误导致操作未完成,程序应该做到如下:
为了能够使程序处理异常情况,必须研究程序中可能出现的错误和问题,以及哪一类问题需要关注。
异常对象都是派生于Throwable类的一个实例。用户也可以自己创建异常类:
需要注意,所有异常都是由Throwable继承而来,分解为两个分支:Error和Exception。
Error类层次的结构描述了Java运行系统的内部错误和资源消耗错误。程序不可以抛出这种类型的对象,若出现了这种内部错误,不仅要通知用户,并且还应该尽力使程序安全终止。
Exception 层次结构,这个层次结构又分解为两个分支:一个分支派生于 RuntimeException ; 另一个分支包含其他异常。划分两个分支的规则是:由程序错误导致的异常属于 RuntimeException ; 而程序本身没有问题, 但由于像 I/O 错误这类问题导致的异常属于其他异常。
派生于RuntimeException的异常包含如下情况:
不是派生于RuntimeException的异常包括:
Java语言规范将派生于Error类的RuntimeException类的所有异常都称之为非受查unchecked异常,所有的其他异常称之为受查异常。编译器会检查是否给所有的受查异常提供了异常处理器。
C++ 注释:如果熟悉标准 C++ 类库中的异常层次结构,就一定会感到有些困惑。C++ 有两个基本的异常类,一个是 rantime_error ;一个是 logic_error。logic_error 类相当于Java 中的 RuntimeException,它表示程今中的逻辑错误;mntime_error 类是所有由于不可预测的原因所引发的异常的基类。它相当于 Java 中的非 RuntimeException 异常。
若遇到了无法处理情况,则Java方法会抛出一个异常。一个方法不仅要告诉编译器返回什么值,也要告诉编译器可能发生什么错误。例如读取文件代码可能知道文件不存在,或者内容空,因此,处理文件信息代码需要告知编译器抛出IOException异常。
方法应该在其首部声明所有可能抛出的异常。这样可以从首:部反映出这个方法可能抛出哪类受査异常。例如,下面是标准类库中提供的 FilelnputStream 类的一个构造器的声明:
public Fi1elnputStream(String name) throws FileNotFoundException
这个声明表7K这个构造器将根据给定的 String 参数产生一个 FilelnputStream 对象,但也有可能抛出一个 FileNotFoimdExc印tion 异常。如果发生了这种糟糕情况,构造器将不会初始化一个新的 Hlel叩utStream 对象,而是抛出一个 FileNotFoundException 类对象。 如果这个方法真的抛出了这样一个异常对象,运行时系统就会开始搜索异常处理器,以便知道如何处理FileNotFoundException 对象„
另外我们需要记住遇到下面这4种情况应该给出异常:
如果出现前两种情况,则必须告诉调用这个方法程序员可能抛出异常,这是由于任何一个抛出异常的方法都可能是一个死亡陷阱,若没有处理器捕获异常,则执行线程可能结束。
警告:如果在子类中覆盖了超类的一个方法,子类方法中声明的受查异常不能比超类方法中声明的异常更通用(也就是说,子类方法中可以抛出更特定的异常,或者根本不抛出任何异常)。特别需要说明的是,如果超类方法没有抛出任何受查异常,子类也不能抛出任何受查异常。例如,如果覆盖 JComponent.paintComponent 方法,由于超类中这个方法没有抛出任何异常,所以,自定义的 paintComponent 也不能抛出任何受查异常。
若类种有一个方法声明会抛出异常,而异常是某个特定实例,则这个方法会抛出这一个类的异常,或者这个类的任意一个子类的异常。
例如FileInputStream构造器声明将有可能抛出一个IOException异常,然而并不知道具体是哪种IOException异常。
C++ 注释:Java 中的 throws 说明符与 C++ 中的 throw 说明符基本类似,但有一点重要的区别。在 C++ 中,throw 说明符在运行时执行,而不是在编译时执行。也就是说,C++编译器将不处理任何异常规范。但是,如果函数抛出的异常没有出现在 throw 列表中,就会调用 unexpected 函数,这个函数的默认处理方式是终止程序的执行。另外,在 C++ 中,如果没有给出 throw 说明,函数可能会抛出任何异常。而在 Java中,没有 throws 说明符的方法将不能抛出任何受查异常。
首先决定抛出什么类型的异常,把上述异常归结为IOException是一种很好的选择,EOFException异常描述的是:“输入过程中,遇到了一个未预期的EOF之后得信号。”,这正是我们要抛出的异常,给出异常语句:
throw new EOFException();
下面这些代码放在一起:
String readData(Scanner in) throws EOFException {
....
while (....) {
if (!in.hasNext()) {
throws new EOFException();
}
....
}
return s;
}
EOFException类还有一个含有一个字符串型参数的构造器.。这个构造器可以更加细致的描述异常出现的情况。
对于一个存在的异常类,抛出则非常容易:
在程序中,可能会遇到任何标准异常类都没有能够充分地描述清楚的问题。 在这种情况下,创建自己的异常类就是一件顺理成章的事情了我们需要做的只是定义一个派生于Exception 的类,或者派生于 Exception 子类的类。例如,定义一个派生于 IOException 的类。习惯上,定义的类应该包含两个构造器,一个是默认的构造器;另一个是带有详细描述信息的构造器(超类 Throwable 的 toString 方法将会打印出这些详细信息,这在调试中非常有用)。
class FileFormatException extends IOException {
public FileFormatException() {}
public FileFormatException() {
super(gripe);
}
}
现在就可以自定义异常类了:
String readData(BufferedReader in) throws FileFormatException {
.....
while (....) {
if (ch == -1) {
//EOF countered
if (n < len) {
throw new FileFormatException();
}
}
}
}
java.lang.Throwable 1.0 API
- Throwable:构造一个新的Throwable对象,这个对象没有详细的信息。
- Throwable(String message):构造一个新的throwable对象,带有特定的详细描述信息,所有派生的异常类都支持一个默认的构造器和一个带有详细信息的构造器。
- String getMessage():获取Throwable对象的详细描述信息。