需要错误源能通过某种方式,把适当的信息传递给某个接受者——该接受者知道如何正确地处理这个问题。
异常处理是Java中唯一正式的错误报告机制,并且通过编译器强制执行。
1 概念
- “异常”这个词有“我对此感到意外”的意思。问题出现了,你也许并不清楚该如何处理,但你的确知道不应该置之不理;你要停下来,看看是不是有别人或在别的地方,能够处理这个问题。只是在当前的环境中还没有足够的信息来解决这个问题,所以就把这个问题提交到一个更高级别的环境中,在这里将作出正确的决定。
2 基本异常
- 当抛出异常后,首先,同Java中其它的对象创建一样,将使用 new在堆上创建异常对象。然后,当前的执行路径(它不能继续下去了)被终止,并且从当前环境中弹出对异常对象的引用。此时,异常处理机制接管程序,并开始寻找一个恰当的地方来继续执行程序。这个恰当的地方就是异常处理程序,它的任务是将程序从错误状态中恢复,以使程序能要么换一种方式运行,要么继续运行下去。
- 异常允许我们(如果没有其它手段)强制程序停止运行,并告诉我们出现了什么问题,或者(理想状态下)强制程序处理问题,并返回到稳定状态。
2.1 异常参数
- 我们总是用 new 在堆上创建异常对象,这也伴随着存储空间的分配和构造器的调用。所有标准异常类都有两个构造器:一个是默认构造器;另一个是接受字符串作为参数,以便能把相关信息放入异常对象的构造器:
throw new NullpointerException("t = null ");
在使用 new 创建异常对象之后,此对象的引用将传给 throw。将会返回一个异常对象然后退出方法或作用域。 - 能够抛出任意类型的Throwable对象,它是异常类型的根类。通常,对于不同类型的错误,要抛出相应的异常。错误信息可以保存在异常对象内部或者使用异常类的名称来暗示。上一层环境通过这些信息来决定如何处理异常。(通常,异常对象中仅有的信息就是异常类型,除此之外不包含任何有意义的内容。)
3 捕获异常
要明白异常是如何被捕获的,必须首先理解监控区域的概念。它是一段可能产生异常的代码,并且后面跟着处理这些异常的代码。
3.1 try块
如果在方法内部抛出了异常(或者在方法内部调用的其他方法抛出了异常),这个方法将在抛出异常的过程中结束。要是不希望方法就此结束,可以在方法内设置一个特殊的块来捕获异常。
try{
//Code that might generate exceptions
}
3.2 异常处理程序
当然,抛出的异常必须在某处得到处理。这个“地点”就是异常处理程序,而且针对每个要捕获的异常,得准备相应的处理程序。异常处理程序紧跟在 try 块之后,以关键字 catch 表示:
try{
//Code that might generate exceptions
}catch(Type1 id1){
//handle exceptions of Type1
}catch(Type2 id2){
//handle exceptions of Type2
}
//etc...
异常处理程序必须紧跟在 try 块之后。当异常被抛出时,异常处理机制将负责搜寻参数与异常类型相匹配的第一个处理程序。然后进入 catch 子句执行,此时认为异常得到了处理。一旦 catch 子句结束,则处理程序的查找过程结束。注意,只有匹配的 catch 子句才能得到执行。
4 创建自定义异常
要自己定义异常类,必须从已有的异常类继承,最好是选择意思相近的异常类继承。建立新的异常类最简单的方法就是让编译器为你产生默认的构造器。
class simpleException extends Exception{} //简单的自定义异常
对异常来说,最重要的部分就是类名。
5 异常说明
- Java鼓励人们把方法可能会抛出的异常告知使用此方法的客户端程序员。这是种优雅的做法,它使得调用者能确切知道写什么样的代码可以捕获所有潜在的异常。异常说明,它属于方法声明的一部分,紧跟在形式参数列表之后。
- 代码必须与异常说明保持一致。如果方法里的代码产生了异常却没有进行处理,编译器会发现这个问题并提醒你:要么处理这个异常,要么就在异常说明中表明此方法将产生异常。
6 捕获异常
最好把异常类型的基类Exception放在异常处理程序列表的末尾,以防它抢在其他处理程序之前先把异常捕获了。
6.1 栈轨迹
void printStackTrace() //输出到标准错误
void printStackTrace(PrintStream)
void printStackTrace(java.io.PrintWriter) //选择要输出的流
打印Throwable和Throwable的栈轨迹。调用栈显示了“把你带到异常抛出的地点”的方法调用序列。
6.2 重新抛出异常
既然已经得到了对当前对象的引用,可以直接把它重新抛出:
catch(Exception e){
//e.printStackTrace();
throw e;
}
重抛异常会把异常抛给上一级环境中的异常处理程序,同一个 try 块的后续 catch 子句将被忽略。此外,异常对象的所有信息得以保持,所以高一级环境中捕获此异常的处理程序可以从这个异常对象中得到所有信息。
如果只是把当前异常对象重新抛出,那么printStackTrace()方法显示的将是原来异常抛出点的调用栈信息,而并非重新抛出点的信息。要想更新这个信息,可以调用fillInStackTrace()方法,这将返回一个Throwable对象,它是通过把当前调用栈信息填入原来那个异常对象而建立的:
public class Rethrowing{
public static void f() throws Exception{
System.out.println("originating the exception in f()");
throw new Exception("thrown from f()");
}
public static void g() throws Exception{
try{
f();
}catch(Exception e){
System.out.println("Inside g(),e.printStackTrace()");
e.printStackTrace(System.out);
throw e;
}
}
public static void h() throws Exception{
try{
f();
}catch(Exception e){
System.out.println("Inside h(),e.printStackTrace()");
e.printStackTrace(System.out);
throw (Exception)e.fillInStackTrace();
}
}
public static void main(String[] args){
try{
g();
}catch(Exception e){
System.out.println("main:printStackTrace()");
e.printStackTrace(System.out);
}
try{
h();
}catch(Exception e){
System.out.println("main:printStackTrace()");
e.printStackTrace(System.out);
}
}
}
/*
输出结果为:
originating the exception in f()
Inside g(),e.printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:4)
at Rethrowing.g(Rethrowing.java:8)
at Rethrowing.main(Rethrowing.java:26)
main:printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:4)
at Rethrowing.g(Rethrowing.java:8)
at Rethrowing.main(Rethrowing.java:26)
originating the exception in f()
Inside h(),e.printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:4)
at Rethrowing.h(Rethrowing.java:17)
at Rethrowing.main(Rethrowing.java:33)
main:printStackTrace()
java.lang.Exception: thrown from f() //调用fillInStackTrace()后就变成了异常新的发生地
at Rethrowing.h(Rethrowing.java:21)
at Rethrowing.main(Rethrowing.java:33)
*/
6.3 异常链
常常会想要在捕获一个异常后抛出另一个异常,并且希望把原始异常的信息保存下来,这被称为异常链。
现在所有Throwable的子类在构造器中都可以接受一个cause(因由)对象作为参数。这个cause就用来表示原始异常,这样通过把原始异常传递给新的异常,使得即使在当前位置创建并抛出了新的异常,也能通过这个异常链追踪到异常最初发生的位置。
在Throwable的子类中,只有三种基本的异常类提供了带cause参数的构造器。它们是Error(用于Java虚拟机报告系统错误)、Exception、以及RuntimeException。如果要把其他类型的异常链接起来,应该使用initCause()方法而不是构造器。
* 只能在代码中忽略RuntimeException(及其子类)类型的异常,其它类型异常的处理都是由编译器强制实施的。究其原因,RuntimeException代表的是编程错误。*