常用的异常有:Error、Exception,这两个类的基类都是Throwable。
其中Error是用来表示编译时和系统的错误,这一类问题,基本不需要我们关心。
Exception就是我们常见的异常。由源码可知,Exception类自身并没有什么重要的东西,它只是Throwable类的一个子类。
public class Exception extends Throwable {
static final long serialVersionUID = -3387516993124229948L;
public Exception() {
super();
}
public Exception(String message) {
super(message);
}
public Exception(String message, Throwable cause) {
super(message, cause);
}
public Exception(Throwable cause) {
super(cause);
}
protected Exception(String message, Throwable cause,
boolean enableSuppression,
boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
因此我们学习异常的源码重心就放在了Throwable类上。
一个异常类,主要的成员属性有:异常名,以及描述信息,和异常的栈帧追踪。
异常名:告诉我们是什么异常,通常用类名表示,比如IOException,这是IO异常。
描述信息:则是该异常的具体描述,是如何发生的,比如网络连接失败等等。
异常的栈帧追踪:这记录了异常发生的代码位置。一般从main函数开始,记录每一步函数调用,一直到最后导致异常的代码。
public class Throwable implements Serializable {
...
private String detailMessage; /** 异常的具体细节*/
/** 构造一个空的StackTrace数组 stackTrace */
private static final StackTraceElement[] UNASSIGNED_STACK = new StackTraceElement[0];
private StackTraceElement[] stackTrace = UNASSIGNED_STACK; /** 栈帧追踪 */
...
/** 字符串s则是异常名,输出时直接输出异常的类型。比如IOException*/
public String toString() {
String s = getClass().getName();
String message = getLocalizedMessage();
return (message != null) ? (s + ": " + message) : s;
}
}
stackTrace,栈帧追踪,是我们在debug时,经常需要获取的信息。可以帮我们定位bug的位置。
那么Throwable是如何操作该成员属性的呢。(这学习源码之前,我最好奇的地方,傻傻地猜想是用数组记录了每一个方法调用,可是很显然,这样不科学)
首先交代:stackTrace是由本地方法获得的,相关的两个本地方法如下,万恶的native是吧,笔者认为是和JVM有关。
/** 本地方法,用于跟踪异常的栈帧 */
native int getStackTraceDepth(); /** 获得追踪数组的深度 */
native StackTraceElement getStackTraceElement(int index); /** 获得下标为index的栈帧信息*/
具体如何使用这两个本地方法或者stackTrace,这个我们还是可以从源码中看到的。
getStackTrace():是一个公开的接口,给外界调用。
getOurStackTrace(): 则是真正的执行方法。先是查看stackTrace是否为UNASSIGNED_STACK(初始状态),或者是否为空,并且有追踪标志。如果满足,则可以调用本地方法得到数组深度depth,来分配stackTrace的空间,并将数组成员一个一个写入stackTrace中。
/**
* 获取当前状态异常的信息,
* 获取栈上面的异常信息,(通过本地方法得到)
* 遍历每一个异常信息,
* 赋值给stackTrace进行保存
* */
public StackTraceElement[] getStackTrace() {
return getOurStackTrace().clone();
}
private synchronized StackTraceElement[] getOurStackTrace() {
if (stackTrace == UNASSIGNED_STACK ||
(stackTrace == null && backtrace != null) /* Out of protocol state */) {
int depth = getStackTraceDepth(); /** 通过本地方法得到栈帧数组的深度 depth*/
stackTrace = new StackTraceElement[depth]; /** 初始化一个长度为depth的栈帧数组*/
for (int i=0; i < depth; i++)
stackTrace[i] = getStackTraceElement(i); /** 得到本地方法中的栈异常清空,复值给stackTrace[i]*/
} else if (stackTrace == null) {
return UNASSIGNED_STACK;
}
return stackTrace; /** 返回栈帧数组 */
}
我们在遇到异常时,经常需要将异常的栈帧追踪打印出来,使用printStackTrace()。该方法,可以设置一个输出流参数,比如System.out。将异常输出到控制台上,也可以缺省,在缺省的情况下,默认输出路径为System.err。具体代码如下:
/**************************************************
* 打印异常栈,从方法调用出直到异常抛出处
* 可选参数:PrintStream s ;PrintWriter s。一个输出流。
* 默认输出到System.err
**************************************************/
public void printStackTrace() {
printStackTrace(System.err);
}
public void printStackTrace(PrintStream s) {
printStackTrace(new WrappedPrintStream(s));
}
/** 打印异常信息 */
private void printStackTrace(PrintStreamOrWriter s) {
// Guard against malicious overrides of Throwable.equals by
// using a Set with identity equality semantics.
Set<Throwable> dejaVu =
Collections.newSetFromMap(new IdentityHashMap<Throwable, Boolean>());
dejaVu.add(this);
synchronized (s.lock()) {
// Print our stack trace
s.println(this);
StackTraceElement[] trace = getOurStackTrace(); /** 打印栈帧情况*/
for (StackTraceElement traceElement : trace)
s.println("\tat " + traceElement);
// Print suppressed exceptions, if any
for (Throwable se : getSuppressed()) /** 打印被压抑的异常(as try) */
se.printEnclosedStackTrace(s, trace, SUPPRESSED_CAPTION, "\t", dejaVu);
// Print cause, if any
Throwable ourCause = getCause(); /** 打印异常链*/
if (ourCause != null)
ourCause.printEnclosedStackTrace(s, trace, CAUSE_CAPTION, "", dejaVu);
}
}
在这里,有一个getSupressed(),该方法是获得被压抑的异常。被压抑的异常在笔者理解中是在try()catch(){}中捕获的异常。
与压抑异常相对应的就是RuntimeException,该异常是运行时由于代码问题而产生的异常(比如数组越界等等,也称不受检查异常,即不需要我们进行检查、捕获,程序直接会抛出异常,并终止程序)
异常链。则可以理解为一个异常构成的链表。其实就是在处理某个异常A时,导致了另一个异常B的产生。那么B异常的成员变量cause则为异常A。
此外还有一个较为常用的方法。 fillInStackTrace()。该方法顾名思义就是将跟踪栈帧进行清空。底层实现也是本地方法。常用于将异常抛给上级处理,如调用该方法,则会重新设置栈帧跟踪。但是,描述信息和异常名不变。
/**
* 清空本异常之前的栈帧跟踪,通常用于向上级异常处理进行 抛出异常,之后调用
* 保留原异常的名称和描述
* 从抛出异常处重新设置栈帧跟踪。
*
* */
public synchronized Throwable fillInStackTrace() {
if (stackTrace != null ||
backtrace != null /* Out of protocol state */ ) {
fillInStackTrace(0);
stackTrace = UNASSIGNED_STACK;
}
return this;
}
private native Throwable fillInStackTrace(int dummy);