6.2 异常类

6.2 异常类

NullPointerException和NumberFormatException都是异常类,所有异常类都有一个共同的父类Throwable,我们先来介绍这个父类,然后介绍Java中的异常类体系,最后介绍怎么自定义异常。

6.2.1 Throwable

NullPointerException和NumberFormatException有一个共同的父类Throwable,它有4个public构造方法:

1
2
3
4
1.     public Throwable()
2. public Throwable(String message)
3. public Throwable(String message, Throwable cause)
4. public Throwable(Throwable cause)

Throwable类有两个主要参数:一个是message,表示异常消息;另一个是cause,表示触发该异常的其他异常。异常可以形成一个异常链,上层的异常由底层异常触发,cause表示底层异常。Throwable还有一个public方法用于设置cause:

1
Throwable initCause(Throwable cause)

Throwable的某些子类没有带cause参数的构造方法,就可以通过这个方法来设置,这个方法最多只能被调用一次。在所有构造方法的内部,都有一句重要的函数调用:

1
fillInStackTrace();

它会将异常栈信息保存下来,这是我们能看到异常栈的关键。Throwable有一些常用方法用于获取异常信息,比如:

1
2
3
4
5
6
7
8
void printStackTrace() //打印异常栈信息到标准错误输出流
//打印栈信息到指定的流,PrintStream和PrintWriter在第13章介绍
void printStackTrace(PrintStream s)
void printStackTrace(PrintWriter s)
String getMessage() //获取设置的异常message
Throwable getCause() //获取异常的cause
//获取异常栈每一层的信息, 每个StackTraceElement包括文件名、类名、函数名、行号等信息
StackTraceElement[] getStackTrace()

6.2.2 异常类体系

以Throwable为根,Java定义了非常多的异常类,表示各种类型的异常,部分类如图6-1所示。

epub_923038_47

图6-1 Java异常类体系

Throwable是所有异常的基类,它有两个子类:Error和Exception。

Error表示系统错误或资源耗尽,由Java系统自己使用,应用程序不应抛出和处理,比如图6-1中列出的虚拟机错误(VirtualMacheError)及其子类内存溢出错误(OutOfMemory-Error)和栈溢出错误(StackOverflowError)。

Exception表示应用程序错误,它有很多子类,应用程序也可以通过继承Exception或其子类创建自定义异常,图6-1中列出了三个直接子类:IOException(输入输出I/O异常)、RuntimeException(运行时异常)、SQLException(数据库SQL异常)。

RuntimeException比较特殊,它的名字有点误导,因为其他异常也是运行时产生的,它表示的实际含义是未受检异常(unchecked exception),相对而言,Exception的其他子类和Exception自身则是受检异常(checked exception),Error及其子类也是未受检异常。

受检(checked)和未受检(unchecked)的区别在于Java如何处理这两种异常。对于受检异常,Java会强制要求程序员进行处理,否则会有编译错误,而对于未受检异常则没有这个要求。下文我们会进一步解释。

RuntimeException也有很多子类,表6-1列出了其中常见的一些。

表6-1 常见的RuntimeException

epub_923038_48
如此多不同的异常类其实并没有比Throwable这个基类多多少属性和方法,大部分类在继承父类后只是定义了几个构造方法,这些构造方法也只是调用了父类的构造方法,并没有额外的操作。

那为什么定义这么多不同的类呢?主要是为了名字不同。异常类的名字本身就代表了异常的关键信息,无论是抛出还是捕获异常,使用合适的名字都有助于代码的可读性和可维护性。

6.2.3 自定义异常

除了Java API中定义的异常类,也可以自己定义异常类,一般是继承Exception或者它的某个子类。如果父类是RuntimeException或它的某个子类,则自定义异常也是未受检异常;如果是Exception或Exception的其他子类,则自定义异常是受检异常。

我们通过继承Exception来定义一个异常,如代码清单6-2所示。

代码清单6-2 自定义异常示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class AppException extends Exception {
public AppException() {
super();
}
public AppException(String message, Throwable cause) {
super(message, cause);
}
public AppException(String message) {
super(message);
}
public AppException(Throwable cause) {
super(cause);
}
}

和很多其他异常类一样,我们没有定义额外的属性和代码,只是继承了Exception,定义了构造方法并调用了父类的构造方法。