异常对象都是派生于Throwable 类的一个实例。
异常层次结构简化示意图:
所有的异常都是由Throwable 继承而来,但在下一层立即分解为两个分支:Error 和 Exception
Error类层次结构描述了Java运行时系统的 内部错误 和 资源耗尽错误
Exception 层次分解为两个分支: RuntimeException 和 其他异常
Java语言规范将派生于 Error 异常或 RuntimeException 类的所有异常称为 非受查(unchecked) 异常 。
所有其他的异常称为 受查(checked)异常 。
子类方法中声明的受查异常并不能比超类方法中声明的异常更通用,即子类方法中可抛出更特定的异常,或者根本不抛出任何异常。特别声明:如果超类方法没有抛出任何受查异常,子类也不能抛出任何受查异常。
class FileFormatException extends IOException { public FileFormatException(){} public FileFormatException(String message) { super(message); } } 复制代码
一般异常处理最好的选择,就是将异常传递给调用者,让调用者自己去操心。
在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字句包含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 。
待资源的try 语句(try-with-resources) 的最简形式
try(Resource res = ...) { work with res } 复制代码
指定多个资源:
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 语句。
堆栈轨迹是一个方法调用过程的列表,它包含了程序执行过程中方法调用的特定位置。
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): 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块中,这样,当任何一个操作出现问题时,整个任务都可以取消。
在java中,往往强化地倾向关闭异常。
例如,当栈空时,Stack.pop 是要返回一个null,还是抛出一个异常?我们认为:在出错的地方抛出一个 EmptyStackException异常要比在后面抛出一个 NullPointerException 异常更好。