一、异常格式
错误堆栈信息的格式大致如下:
第一行包含了错误类型(Exception或Error)和错误描述。
从第二行开始,每一行都表示一个调用栈帧(Stack Frame),包含了类名、方法名和代码行号。
二、异常详细说明
执行上面demo的main函数,输出结果如下:
既然是堆栈,那肯定是FILO(先进后出)的,了解过线程堆栈(比如函数调用的时候参数和局部变量的入栈和出栈)的同学应该比较清楚。所以我们看打出来的异常堆栈的顺序,也应该是从下往上看,就是从第12行开始往上看,一直看到第2行,发现第10行才是问题的根源,异常是一层一层地往外抛出,直至抛出到最外层(即没有catch为止)。第2行的RuntimeException只是真正异常的一个外层表现而已,异常的根源还是要看堆栈最底部的信息。
本demo异常抛出的流程大概如下:
所以,Caused by(中文是“由…造成的”意思)也就比较好理解了,就是这个异常时由哪里触发的意思。fun3的异常是由fun2的异常触发的,而fun2的异常又是由fun1的异常触发的。
解决了异常堆栈查看顺序的问题,我们细看上面demo打印出来的异常堆栈信息,会发现,打印出来的堆栈信息不完整,比如第8行的“ … 1 more”和第12行的“… 2 more”,为什么jvm会省略部分堆栈信息呢?
1.为什么有些堆栈信息被隐藏了?… 2 more
其实“... n more”的部分是重复的堆栈部分。我们分析一下上面这个函数“printEnclosedStackTrace”,翻译为“打印封闭堆栈跟踪信息”,“封闭”暂且可以理解为“完整的”,这个函数有两个比较重要的变量,分别是“enclosingTrace”和“trace ”,这两个参数是什么关系呢?其实可以简单理解为“enclosingTrace”是“trace ”的父级堆栈,函数printEnclosedStackTrace中的while循环,就是为倒序找出“enclosingTrace”和“trace ”中从哪一个栈帧开始就不一样了,即“enclosingTrace”和“trace ”是有一部分是一样的(从数组后面倒回来),就是为了算出有多少个栈帧信息是重复可以隐藏的,相同的栈帧就不用重复输出啦。
三、异常基础知识
异常通常分为两类:
受检异常(Checked Exception):这些异常是在编译时检查的,必须在代码中进行显式处理。例如,IOException、SQLException等。
非受检异常(Unchecked Exception):这些异常是在运行时抛出的,不需要在代码中显式处理。例如,NullPointerException、ArrayIndexOutOfBoundsException等。
四、如何选择异常类型
五、异常处理
特别说明:
如果我们有一个名为Outer的外部类和一个名为Inner的内部类,那么编译后的字节码文件名为:Outer$Inner.class,下图中的异常信息中就有内部类的展示: