一:关于异常

JAVA异常是在java程序运行的时候遇到非正常的情况而创建的对,它封装了异常信息。java异常的根类为java.lang.Throwable,整个类有两个直接子类java.lang.Error和java.lang.Exception。

Error是程序本身无法恢复的严重错误,一般是虚拟机或者系统运行出现错误,和程序无关。Exception则表示可以被程序捕获并处理的异常错误。

JVM用方法调用栈来跟踪每个线程中一系列的方法调用过程,栈是线程私有的,每一个线程都有一个独立的方法调用栈,该栈保存了每个调用方法的信息。当一个新方法被调用的时候,JVM会把描述该方法的栈结构置入栈顶,位于栈顶的方法为正在执行的方法。当一个JAVA方法正常执行完毕,JVM会从调用栈中弹出该方法的栈结构,然后继续处理前一个方法。如果java方法在执行代码的过程中抛出异常,JVM必须找到能捕获异常的catch块代码,它首先查看当前方法是否存在这样的catch代码块,如果存在就执行该 catch代码块,否则JVM会调用栈中弹处该方法的栈结构,继续到前一个方法中查找合适的catch代码块。最后如果JVM向上追到了当前线程调用的第一个方法(如果是主线程就是main方法),仍然没有找到该异常处理的代码块,该线程就会异常终止。如果该线程是主线程,应用程序也随之终止,此时JVM将把异常直接抛给用户,在用户终端上会看到原始的异常信息。

在这里插入图片描述
Throwable
Java异常体系中根类,有两个重要的子类:Exception(异常)和 Error(错误),二者都是 Java 异常处理的重要子类,各自都包含大量子类。

Error(错误)
是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。

Exception(异常)
是由于程序本身引起的异常,分为受检查异常和Runtime异常,RuntimeException直接继承于Exception,本身以及其子类异常均表示提前无法预知的异常。除了RuntimeException及其子类,剩余的都是受检查异常,编译器在编译期强制我们添加try catch去捕获,否则编译不通过。

二、Throwable源码分析

1、成员变量
    private transient Object backtrace;
    //异常信息
    private String detailMessage;
    //当前异常是由哪个Throwable所引起的
    private Throwable cause = this;
	//引起异常的堆栈跟踪信息
    private StackTraceElement[] stackTrace = UNASSIGNED_STACK;
  • backtrace:这个变量由native方法赋值,用来保存栈信息的轨迹
  • detailMessage:这个变量是描述异常信息,比如new Myexception(“My Exception”),记录的就是我们传进去的描述此异常的描述信息 My Exception。
  • cause :记录当前异常是由哪个异常所引起的,默认是this,可通过构造器自定义。可以通过initCase方法进行修改
public synchronized Throwable initCause(Throwable cause) {
        if (this.cause != this)
            throw new IllegalStateException("Can't overwrite cause with " +
                                            Objects.toString(cause, "a null"), this);
        if (cause == this)
            throw new IllegalArgumentException("Self-causation not permitted", this);
        this.cause = cause;
        return this;
    }

可以看到case只能被修改一次,当发现当前case已经被修改,则会抛出IllegalStateException。默认case=this,如果再次修改case为this也是不允许的。
case一般这样使用

	try {
            Integer.valueOf("a");
        }catch (NumberFormatException e){
            throw new MyException(e);
        }
  • stackTrace 记录当前异常堆栈信息,数组中每一个StackTraceElement表示当前当前方法调用的一个栈帧,表示一次方法调用。StackTraceElement中保存的有当前方法的类名、方法名、文件名、行号信息。
public final class StackTraceElement implements java.io.Serializable {
    private String declaringClass;
    private String methodName;
    private String fileName;
    private int lineNumber;
    }
2、构造函数
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;
    }

Throwable提供了4个public构造器和1个protected构造器(该构造器由JDK1.7引入)。4个public构造器共同点就是都调用了fillInStackTrace方法。

3、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);

fillInStackTrace会首先判断stackTrace是不是为null,如果不为null则会调用native方法fillInStackTrace将当前线程的栈帧信息记录到此Throwable中。那么什么时候为null呢,答案是上面的protected构造器可以指定writableStackTrace为false,这样stackTrace就为null了,就不会调用fillInStackTrace获取堆栈信息。

fillInStackTrace将当前线程的栈帧信息记录到此Throwable中为了理解我们来看一个例子

正常情况下我们抛出RuntimeException,异常打印是带有异常堆栈信息的

public class MyException extends RuntimeException {
    public static void method1(){
        System.out.println("method1");
        method2();
    }
    public static void method2(){
        System.out.println("method2");
        method3();
    }
    public static void method3(){
        System.out.println("method3");
        method4();
    }
    public static void method4(){
        throw new MyException();
    }
    public static void main(String[] args) {
        method1();
    }
}

运行结果:

method1
method2
method3
Exception in thread "main" MyException
	at MyException.method4(MyException.java:20)
	at MyException.method3(MyException.java:17)
	at MyException.method2(MyException.java:13)
	at MyException.method1(MyException.java:9)
	at MyException.main(MyException.java:24)

我们来重写fillInStackTrace方法,来看一下运行结果

public class MyException extends RuntimeException {
    @Override
    public synchronized Throwable fillInStackTrace() {
        return this;
    }
    public static void method1(){
        System.out.println("method1");
        method2();
    }
    public static void method2(){
        System.out.println("method2");
        method3();
    }
    public static void method3(){
        System.out.println("method3");
        method4();
    }
    public static void method4(){
        throw new MyException();
    }
    public static void main(String[] args) {
        method1();
    }
}

输出:

method1
method2
method3
Exception in thread "main" MyException

从例子可以看到fillInStackTrace作用是将当前线程的栈帧信息记录到此Throwable中

4、addSuppressed()和getSuppressed()方法
public final synchronized void addSuppressed(Throwable exception) {
        if (exception == this)
            throw new IllegalArgumentException(SELF_SUPPRESSION_MESSAGE, exception);

        if (exception == null)
            throw new NullPointerException(NULL_CAUSE_MESSAGE);

        if (suppressedExceptions == null) // Suppressed exceptions not recorded
            return;

        if (suppressedExceptions == SUPPRESSED_SENTINEL)
            suppressedExceptions = new ArrayList<>(1);

        suppressedExceptions.add(exception);
    }
public final synchronized Throwable[] getSuppressed() {
        if (suppressedExceptions == SUPPRESSED_SENTINEL ||
            suppressedExceptions == null)
            return EMPTY_THROWABLE_ARRAY;
        else
            return suppressedExceptions.toArray(EMPTY_THROWABLE_ARRAY);
    }

如果try中抛出了异常,在执行流程转移到方法栈上一层之前,finally语句块会执行,但是,如果在finally语句块中又抛出了一个异常,那么这个异常会覆盖掉之前抛出的异常,这点很像finally中return的覆盖。比如下面这个例子:

public class MyTest {
    public static void main(String[] args) {
        try {
            Integer.valueOf("one");
        } catch (NumberFormatException e) {
            throw new RuntimeException("One", e);
        } finally {
            try {
                Integer.valueOf("two");
            } catch (NumberFormatException e) {
                throw new RuntimeException("Two", e);
            }
        }
    }
}

输出:

Exception in thread "main" java.lang.RuntimeException: Two
	at MyTest.main(MyTest.java:12)
Caused by: java.lang.NumberFormatException: For input string: "two"
	at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
	at java.lang.Integer.parseInt(Integer.java:580)
	at java.lang.Integer.valueOf(Integer.java:766)
	at MyTest.main(MyTest.java:10)

Throwable对象提供了addSupperssed和getSupperssed方法,允许把finally语句块中产生的异常通过addSupperssed方法添加到try语句产生的异常中。

public class MyTest {
    public static void main(String[] args) {
        RuntimeException exception1 = null;
        try {
            Integer.valueOf("one");
        } catch (NumberFormatException e) {
            exception1 = new RuntimeException("One", e);
            throw exception1;
        } finally {
            try {
                Integer.valueOf("two");
            } catch (NumberFormatException e) {
                RuntimeException exception2 = new RuntimeException("Two", e);
                exception1.addSuppressed(exception2);
                throw exception1;
            }
        }
    }
}

输出:

Exception in thread "main" java.lang.RuntimeException: One
	at MyTest.main(MyTest.java:8)
	Suppressed: java.lang.RuntimeException: Two
		at MyTest.main(MyTest.java:14)
	Caused by: java.lang.NumberFormatException: For input string: "two"
		at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
		at java.lang.Integer.parseInt(Integer.java:580)
		at java.lang.Integer.valueOf(Integer.java:766)
		at MyTest.main(MyTest.java:12)
Caused by: java.lang.NumberFormatException: For input string: "one"
	at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
	at java.lang.Integer.parseInt(Integer.java:580)
	at java.lang.Integer.valueOf(Integer.java:766)
	at MyTest.main(MyTest.java:6)
5、printStackTrace()方法

printStackTrace()方法分四个方面打印出当前异常信息

  1. 打印出当前异常的详细信息
  2. 打印出异常堆栈中的栈帧信息
  3. 打印出support异常信息
  4. 递归打印出引起当前异常的异常信息
public void printStackTrace() {
        printStackTrace(System.err);
    }
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()) {
            // 打印当前异常的详细信息
            s.println(this);
            // 打印当前堆栈中的栈帧信息
            StackTraceElement[] trace = getOurStackTrace();
            for (StackTraceElement traceElement : trace)
                s.println("\tat " + traceElement);

            // 打印suppressed exceptions
            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);
        }
    }
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐