1.栈轨迹
上篇博客里说到了异常的一些常用方法,其中有三个方法可以打印异常的调用信息,printStackTrace()所提供的信息可通过getStackTrace()来直接访问,这个方法将返回一个由栈轨迹中的元素构成的数组,其中每个元素代表栈中的一帧,元素0是栈顶元素,并且是调用序列中的最后一个方法调用(这个Throwable被创建和抛出之处),数组中的第一个元素和栈底是调用序列中的第一个方法调用。
class StackTrace
{
void f() throws Exception{
throw new Exception("Throw from f()");
}
void g() throws Exception{
f();
}
public static void main(String[] args)
{
StackTrace st=new StackTrace();
try{
st.g();
}catch(Exception e){
for(StackTraceElement element:e.getStackTrace()){
System.out.println("Class : "+element.getClassName()+
" Method : "+element.getMethodName());
}
e.printStackTrace();
}
}
}
Output:
可以看到,main方法调用g方法,g方法再调用f方法,这就是异常的栈轨迹。
2.重新抛出异常
异常是像上面那样根据方法依次调用的,而异常的抛出是抛向其上一个方法交由它处理,上面的代码中,g的异常交由f处理,但是f没有进行捕获,就向上抛出了。有时可以在g中捕获之后,再次抛出,那么这次抛出的异常是由它的上一级来处理,也就是main方法。
void g() throws Exception{
try{
f();
}catch(Exception e){
System.out.println("g() handle exception from f() then throw again");
throw e;//抛出的异常为从f中捕获的异常,异常信息没有变化
//throw new Exception("Throw from g()");//抛出的异常为一个新的异常,异常信息与从f中抛出的异常不同
//throw (Exception)e.fillInStackTrace();//抛出的异常为捕获的异常,但是却更新了异常信息。
}
}
将1中的代码中的g方法改成上述代码,并将main方法中catch中的foreach注释掉,运行结果如下:
因为在g中抛出的异常是从f中捕获的异常,所以异常信息并没有与1中的发生变化,但是将上述代码中的第一行注释段去掉后,也就是在g中抛出了一个新的异常,那么main捕获的自然也是一个新的异常,异常信息自然也不一样了,结果如下:
如果既想重新抛出之前捕获的异常又想更新栈轨迹信息,那么可以使用fillInStackTrace放。将上述代码第二行代码注释去掉后,运行结果如下:
3.异常链
常常会想要在捕获一个异常后抛出另一个异常,并且希望把原始异常的信息保存下来,这被称为异常链。JDK 1.4之后,所有的Throwable子类在构造器中接受一个cause对象作为参数,这个cause就用来表示原始异常,这样通过把原始异常传递给新的异常,使得即使在当前位置创建并抛出了新的异常,也能通过这个异常追踪到异常最初发生的位置。
将上述g方法中的异常处理程序抛出的异常代码改为
throw new Exception("Throw from g",e);
运行结果为:
可以看到除了新的异常外,还有cause代表的原始异常。
在Throwable子类中,只有三种基本的异常类提供了带cause参数的构造器,它们是Error(用于Java虚拟机报告系统错误)、Exception以及RuntimeException。如果要把其他的异常链接起来,应该使用initCause()方法而不是构造器。
void g() throws IOException{
try{
f();
}catch(Exception e){
System.out.println("g() handle exception from f() then throw again");
throw (IOException)new IOException("Throw from g").initCause(e);
}
}
将g方法改成上述代码,抛出IOException,并且使用initCause方法将原始异常作为参数。运行结果如下:
4.总结
1)打印异常栈轨迹可以使用printStackTrace和getStackTrace方法等进行处理。
2)重新抛出捕获的异常与抛出一个新的异常的区别;既要更新栈调用信息,又想重新抛出之前捕获的异常需要使用fillInStackTrace方法。
3)异常链是在捕获一个异常后抛出一个新的异常,并将原始异常的信息保存下来。
Error、Exception和RuntimeException可以使用构造器将cause作为参数,而其他的异常则需要使用initCause方法将原始异常作为参数。