在Java中,错误分为两种,一种是jvm能处理的错误,叫做异常,Java中表示Exception类;而另外一种则是jvm不能处理的错误,叫做错误,Java中表示为Error类。它们三者的关系以及常见的子类的实现如下图:
Throwable类是Java中一切Exception类与Error类的父类,它直接以Native方法与jvm进行交互,从jvm中获取java程序运行时的异常和错误信息,并在console中打印出来,其中它的主要成员与构造方法如下:
public class Throwable implements Serializable {
//用来保存一些栈的回溯点
private transient Object backtrace;
//对throwble的描述
private String detailMessage;
//引发该throwable的Throwable类,默认为自身
private Throwable cause = this;
//用栈来保存异常的发生顺序
private StackTraceElement[] stackTrace = UNASSIGNED_STACK;
public Throwable() {
fillInStackTrace();
}
public Throwable(String message) {
fillInStackTrace();
detailMessage = message;
}
public Throwable(String message, Throwable cause) {
fillInStackTrace();
detailMessage = message;
this.cause = cause;
}
public Throwable(Throwable cause) {
fillInStackTrace();
detailMessage = (cause==null ? null : cause.toString());
this.cause = cause;
}
protected Throwable(String message, Throwable cause,
boolean enableSuppression,
boolean writableStackTrace) {
if (writableStackTrace) {
fillInStackTrace();
} else {
stackTrace = null;
}
detailMessage = message;
this.cause = cause;
if (!enableSuppression)
suppressedExceptions = null;
}
public String getMessage() {
return detailMessage;
}
//获取其异常的信息
public String getLocalizedMessage() {
return getMessage();
}
//返回引发异常的原因异常类
public synchronized Throwable getCause() {
return (cause==this ? null : cause);
}
}
其中的cause成员变量是用来存贮引发某个异常或错误的异常或错误类的,也就是说它保存着引发异常或错误的原因异常类,如果没有赋值的话,默认是其自身,默认没有造成当前异常的异常类,也就是getCause方法等于默认的时候,返回的是null的原因,而且getCause是一个同步方法,保证了线程的安全,因为异常或错误信息的返回是有一个延时性的,防止了返回信息的时候遭到了其他子线程的修改。这也形成了一个因果关系,并且可以形成一条因果异常链,即我的throable是你引起的,而你的thowable是他引起的,这就构成了一条因果链了。而fillInStackTrace方法则是调用了Native方法,从jvm中将程序运行轨迹进栈,并返回一个异常,这个栈是在jvm中的,而在java程序中是用StackTraceElement栈来保存:
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);
从jvm中取出程序运行的轨迹的代码如下:
//获取栈中某个元素
native StackTraceElement getStackTraceElement(int index);
//获取栈的长度
native int getStackTraceDepth();
public StackTraceElement[] getStackTrace() {
return getOurStackTrace().clone();
}
private synchronized StackTraceElement[] getOurStackTrace() {
// Initialize stack trace field with information from
// backtrace if this is the first call to this method
if (stackTrace == UNASSIGNED_STACK ||
(stackTrace == null && backtrace != null) /* Out of protocol state */) {
int depth = getStackTraceDepth();
stackTrace = new StackTraceElement[depth];
for (int i=0; i < depth; i++)
stackTrace[i] = getStackTraceElement(i);
} else if (stackTrace == null) {
return UNASSIGNED_STACK;
}
return stackTrace;
}
StackTraceElement栈保存的主要元素有:
public final class StackTraceElement implements java.io.Serializable {
private String declaringClass;
private String methodName;
private String fileName;
private int lineNumber;
}
从源码中可以得出,StackTraceElement保存的是每个类描述、调用这个类的方法、该类的文件名、以及代码行数。也就是说StackTraceElement保存的是从fillInStackTrace方法得到的程序执行轨迹中的类与方法的执行顺序情况。而在打印异常或错误信息的时候,是按照栈的先进后出的原则打印的,而且打印方法是递归打印的:
private static final String NULL_CAUSE_MESSAGE = "Cannot suppress a null exception.";
private static final String SELF_SUPPRESSION_MESSAGE = "Self-suppression not permitted";
private static final String CAUSE_CAPTION = "Caused by: ";
private static final String SUPPRESSED_CAPTION = "Suppressed: ";
private void printStackTrace(PrintStreamOrWriter s) {
······
//同步
synchronized (s.lock()) {
//打印有该异常类的造成影响的异常类,也就是上层的异常类
for (Throwable se : getSuppressed())
se.printEnclosedStackTrace(s, trace, SUPPRESSED_CAPTION, "\t", dejaVu);
//打印造成该异常类的异常类,如果有的话
Throwable ourCause = getCause();
if (ourCause != null)
ourCause.printEnclosedStackTrace(s, trace, CAUSE_CAPTION, "", dejaVu);
}
}
private void printEnclosedStackTrace(PrintStreamOrWriter s,
StackTraceElement[] enclosingTrace,
String caption,
String prefix,
Set<Throwable> dejaVu) {
//确保有锁
assert Thread.holdsLock(s.lock());
//从栈顶开始遍历
int m = trace.length - 1;
int n = enclosingTrace.length - 1;
while (m >= 0 && n >=0 && trace[m].equals(enclosingTrace[n])) {
m--; n--;
}
······
for (Throwable se : getSuppressed())
se.printEnclosedStackTrace(s, trace, SUPPRESSED_CAPTION,
prefix +"\t", dejaVu);
Throwable ourCause = getCause();
if (ourCause != null)
ourCause.printEnclosedStackTrace(s, trace, CAUSE_CAPTION, prefix, dejaVu);
}
}
其中的getSuppressed方法是返回一个该异常类的造成影响的异常类的数组,也就是该异常类的上层异常类,而getCause是返回造成当前异常类的异常类,是比该异常类还下层的异常类,也就是上面说的,形成一个异常链。而栈的先进后出是异常的抛出往往能正确的指定到产生异常的地方的原因,因为这时候程序执行的代码(最新的异常或错误)是最后进栈的,而打印的时候是最先打印的,打印方法也保证了线程的安全。而Throwable源码中的initCause方法是来设置引起当前异常类的异常类的,一个Throwable类只能设置一次cause:
public synchronized Throwable initCause(Throwable cause) {
if (this.cause != this)//已设置过cause,则抛异常,意思为只能设置一次
cause属性
throw new IllegalStateException("Can't overwrite cause");
if (cause == this)//若参数cause等于本身,抛异常
throw new IllegalArgumentException("Self-causation not permitted");
this.cause = cause;
return this;
}
其中异常分为两种,一种是运行时异常,即Runtime异常,一种是检查时异常,即Checked异常。在上图中除了RuntimeException及其子类是Runtime异常,其余的都是Checked异常。 两者的区别是:Checked Exception用来指示一种调用方能够直接处理的异常情况,比如类未找到异常等。 而Runtime Exception则用来指示一种调用方本身无法处理或恢复的程序错误,比如除零异常等。Exception类与Error类只是简单的继承了Throwable类,以Exception类为例:
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);
}
}
那么如何自定义一个Exception类呢?其实很简单,只要继承Exception类,在里面加你自定义类的描述信息就够了,如下:
public class TestException extends Exception{
private String message;
public TestException(String message){
this.message=message;
}
public String toString(){
return message;
}
}
public class Test {
public static void main(String args[]) throws TestException {
int a=9;
if(a<10){
throw new TestException("111");
}
}
}
//输出:Exception in thread "main" 111
//at test.Test.main(Test.java:10)
还有一点注意的是,catch与finally语句块并不会因为return的操作而受到影响:
public static void main(String args[]) throws TestException {
try{
int b=10/0;
return;
}catch (Exception e){
System.out.println("中间");
return;
}finally {
System.out.println("终结");
}
}
//输出为:中间 终结