异常对象都是派生于Throwable 类的一个实例。
异常层次结构简化示意图:

所有的异常都是由Throwable 继承而来,但在下一层立即分解为两个分支:Error 和 Exception
Error类层次结构描述了Java运行时系统的内部错误和资源耗尽错误
Exception 层次
Exception 层次分解为两个分支: RuntimeException 和 其他异常
RuntimeException 异常:
- 错误的类型转换 ClassCastException
- 数组访问越界 ArrayIndexOutOfBoundsException
- 访问null指针 NullPointerException
不是派生于 RuntimeException 异常包括:
- 试图在文件尾部后面读取数据
- 试图打开一个不存在的文件
- 试图根据给定的字符串查找Class对象,而这个字符串表示的类并不存在
Java语言规范将派生于 Error 异常或 RuntimeException 类的所有异常称为非受查(unchecked) 异常。
所有其他的异常称为受查(checked)异常。
什么时候该抛出异常 throws :
- 调用一个抛出受查异常的方法
- 运行时发现错误,利用 throw 语句抛出一个受查异常
- 程序出现错误,如 ArrayIndexOutOfBoundsException 这样的非受查异常
- Java 虚拟机和运行时库出现的内部错误
子类方法中声明的受查异常并不能比超类方法中声明的异常更通用,即子类方法中可抛出更特定的异常,或者根本不抛出任何异常。特别声明:如果超类方法没有抛出任何受查异常,子类也不能抛出任何受查异常。
自定义异常:
- 定义一个派生于Exception的类,或者派生于Exception子类的类。
- 习惯上,定义的类应该包含两个构造器。一个默认的构造器,另一个是带有详细描述信息构造器。
class FileFormatException extends IOException
{
public FileFormatException(){}
public FileFormatException(String message)
{
super(message);
}
}
API java.lang.Throwable
- Throwable() 构造一个新的Throwable 对象,这个对象没有详细的描述信息
- Throwable(String message) 构造一个新的Throwable,这个对象带有特定的详细描述信息。习惯上,所有的派生的异常类都支持一个默认的构造器和一个带有详细信息的构造器。
- String getMessage() 获得Throwable 对象的详细描述信息
异常处理小技巧
一般异常处理最好的选择,就是将异常传递给调用者,让调用者自己去操心。
在catch 字句中可以抛出一个异常,这样做的目的是改变异常的类型。我们可以采用一种比较推荐的处理异常的方法,并且将原始异常设置为新异常的"原因":
try
{
access the database
}
catch(SQLException e)
{
Throwable se = new ServletException("database error");
se.initCause(e);
throw se;
}
当捕获到异常时,就可以使用下面这条语句重新得到原始异常:
Throwable e = se.getCause();
使用这种包装技术,可让用户抛出子系统中的高级异常,而不会丢失原始异常的细节
如果在一个方法中发生了一个受查异常,而不允许抛出它,那包装技术就十分有用。我们可捕获这个受查异常,并将它包装成一个运行时异常。
finally 语句
不管是否有异常被捕获,finally 字句中的代码都被执行。
当finally字句包含return 语句时,将会出现一种意想不到的结果。
假设利用return 语句从try语句块中退出。在方法返回前,finally字句的内容将被执行。如果finally字句中也有一个return语句,这个返回值将会覆盖原始的返回值。例:
public static int f(int n)
{
try{
return n*n;
}finally {
if (2 == n)
return 0;
}
}
如果调用f(2) ,try语句返回结果为4,然而在方法返回前,要执行finally字句。finally字句使得方法返回0。这个返回值覆盖了原先的返回值4。所以调用 f(2) 返回的值为 0。
JAVA SE7 关闭资源的处理
待资源的try 语句(try-with-resources) 的最简形式
try(Resource res = ...)
{
work with res
}
try 块退出时,会自动调用res.close()
指定多个资源:
try(Scanner in = new Scanner(new FileInputStream("/usr/shar/dict/words"),"UTF-8");
PrintWriter out = new PrintWriter("out.txt")){
while (in.hasNext())
out.println(in.next().toUpperCase());
}
不管这个块如何退出,in 和 out 都会关闭。
常规方式手动编程,就需要两个嵌套的try/finally 语句。
堆栈轨迹(stack trace)
堆栈轨迹是一个方法调用过程的列表,它包含了程序执行过程中方法调用的特定位置。
访问堆栈轨迹的文本描述信息
Throwable t = new Throwable();
StackTraceElement[] frames = t.getStackTrace();
for (StackTraceElement frame : frames){
analyze frame
}
StackTraceElement 类含有能够获得文件名和当前执行的代码行号的方法。同时,还含有能够获得类名和方法名的方法。
静态的 Thread.getAllStackTrace 方法,它可以产生所有线程的堆栈轨迹。例
Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
for (Thread t : map.keySet()){
StackTraceElement[] frames = map.get(t);
analyze frames
}
打印一个递归阶乘的函数的堆栈情况
public class StackTraceTest {
/**
* 计算n的阶乘
* @param n
* @return
*/
public static int factorial(int n)
{
System.out.println("factorial(" + n + "):");
Throwable t = new Throwable();
StackTraceElement[] frames = t.getStackTrace();
for (StackTraceElement f: frames)
System.out.println(f);
int r;
if (n<=1)
r =1;
else
r = n * factorial(n-1);
System.out.println("return " + r);
return r;
}
public static void main(String[] args)
{
Scanner in = new Scanner(System.in);
System.out.print("Enter n : ");
int n = in.nextInt();
factorial(n);
}
}
计算factorial(3),打印一下内容
factorial(3):
javabook.StackTraceTest.factorial(StackTraceTest.java:15)
javabook.StackTraceTest.main(StackTraceTest.java:32)
factorial(2):
javabook.StackTraceTest.factorial(StackTraceTest.java:15)
javabook.StackTraceTest.factorial(StackTraceTest.java:23)
javabook.StackTraceTest.main(StackTraceTest.java:32)
factorial(1):
javabook.StackTraceTest.factorial(StackTraceTest.java:15)
javabook.StackTraceTest.factorial(StackTraceTest.java:23)
javabook.StackTraceTest.factorial(StackTraceTest.java:23)
javabook.StackTraceTest.main(StackTraceTest.java:32)
return 1
return 2
return 6
使用异常小技巧
异常处理不能代替简单的测试
与执行简单的测试相比,捕获异常所花费的时间大大超过前者。因此使用异常的基本规则是,旨在异常情况下使用异常机制。
不要过分地细化异常
将整个任务包装在一个try块中,这样,当任何一个操作出现问题时,整个任务都可以取消。
利用异常层次结构
- 不要只抛出 RuntimeException 异常。应该寻找更加适当的子类或创建自己的异常类。
- 不要只捕获Throwable 异常,否则,会使程序代码更难读、更难维护
- 考虑受查异常和非受查异常的区别。
- 将一种异常转换成另一种更加适合的异常时不要犹豫。
不要压制异常
在java中,往往强化地倾向关闭异常。
在检测错误时,“苛刻”要比放任更好
例如,当栈空时,Stack.pop 是要返回一个null,还是抛出一个异常?我们认为:在出错的地方抛出一个 EmptyStackException异常要比在后面抛出一个 NullPointerException 异常更好。
不要羞于传递异常
让高层次的方法通知用户发生了错误,或者放弃不成功的命令更加适宜。