问题:
Throwable是所有异常的父类,那么异常到底包含哪些信息呢?
1.Throwable包含哪些成员变量?
public class Throwable implements Serializable {
private transient volatile Object backtrace;
//异常信息
private String detailMessage;
//当前异常是由哪个Throwable所引起的
private Throwable cause = this;
//引起异常的堆栈跟踪信息
private StackTraceElement[] stackTrace = libcore.util.EmptyArray.STACK_TRACE_ELEMENT;
}
backtrace:这个变量由native方法赋值,用来保存栈信息的轨迹;
detailMessage:这个变量是描述异常信息,比如new InsertFailException("can't insert table"),记录的是传进去描述此异常的描述信息"can't insert table";
case:记录当前异常是由哪个异常所引起的,默认是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 {
lowLevelOp();
} catch (LowLevelException le) {
throw (HighLevelException)
new HighLevelException().initCause(le); // Legacy constructor
}
stackTrace:记录当前异常堆栈信息,数组中每一个StackTraceElement表示当前方法调用的一个栈帧,表示一次方法调用;StackTraceElement中保存的有当前方法的类名,方法名,文件名,行号信息;
public final class StackTraceElement implements java.io.Serializable {
// Normally initialized by VM (public constructor added in 1.5)
private String declaringClass;
private String methodName;
private String fileName;
private int lineNumber;
public String toString() {
// Android-changed: When ART cannot find a line number, the lineNumber field is set
// to the dex_pc and the fileName field is set to null.
StringBuilder result = new StringBuilder();
result.append(getClassName()).append(".").append(methodName);
if (isNativeMethod()) {
result.append("(Native Method)");
} else if (fileName != null) {
if (lineNumber >= 0) {
result.append("(").append(fileName).append(":").append(lineNumber).append(")");
} else {
result.append("(").append(fileName).append(")");
}
} else {
if (lineNumber >= 0) {
// The line number is actually the dex pc.
result.append("(Unknown Source:").append(lineNumber).append(")");
} else {
result.append("(Unknown Source)");
}
}
return result.toString();
}
}
下面代码为打印堆栈信息:
public class Main {
public static void main(String[] args) {
b();
}
public static void b() {
Throwable th = new Throwable();
for (StackTraceElement e : th.getStackTrace()) {
System.out.println(e);
}
}
}
-------运行结果------
com.gome.test.exception.Main.b(Main.java:24)
com.gome.test.exception.Main.main(Main.java:20)
2.Throwable的构造函数
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 */ ) {
backtrace = nativeFillInStackTrace();
stackTrace = libcore.util.EmptyArray.STACK_TRACE_ELEMENT;
}
return this;
}
fillInStackTrace会首先判断stackTrace是否为null,如果不为null则会调用native方法nativeFillInStackTrace()将当前线程的栈帧信息记录到此Throwable中,那么什么时候为null呢,答案是上面的protected构造器可以指定writableStackTrace为false,这样stackTrace就为null了,就不会调用nativeFillInStackTrace获取堆栈信息。
nativeFillInStackTrace将当前线程的栈帧信息记录到此Throwable中,为了理解我们看一个例子:
正常情况下我们抛出RuntimeException,异常打印是带有异常堆栈信息的:
package com.gome.childrenmanager;
public class InsertUserException extends RuntimeException{
public static void insert1(){
System.out.println("Method:insert1()");
insert2();
}
public static void insert2(){
System.out.println("Method:insert2()");
insert3();
}
public static void insert3(){
System.out.println("Method:insert3()");
insert4();
}
public static void insert4(){
throw new InsertUserException();
}
public static void main(String[] args) {
insert1();
}
}
运行结果:
Method:insert1()
Method:insert2()
Method:insert3()
Class transformation time: 0.0137006s for 112 classes or 1.2232678571428573E-4s per class
Exception in thread "main" com.gome.childrenmanager.InsertUserException
at com.gome.childrenmanager.InsertUserException.insert4(InsertUserException.java:19)
at com.gome.childrenmanager.InsertUserException.insert3(InsertUserException.java:16)
at com.gome.childrenmanager.InsertUserException.insert2(InsertUserException.java:11)
at com.gome.childrenmanager.InsertUserException.insert1(InsertUserException.java:6)
at com.gome.childrenmanager.InsertUserException.main(InsertUserException.java:23)
我们来重写fillInStackTrace()方法,看一下运行效果:
public class InsertUserException extends RuntimeException{
@Override
public synchronized Throwable fillInStackTrace() {
return this;
}
public static void insert1(){
System.out.println("Method:insert1()");
insert2();
}
public static void insert2(){
System.out.println("Method:insert2()");
insert3();
}
public static void insert3(){
System.out.println("Method:insert3()");
insert4();
}
public static void insert4(){
throw new InsertUserException();
}
public static void main(String[] args) {
insert1();
}
}
输出:
Method:insert1()
Method:insert2()
Method:insert3()
Class transformation time: 0.0099176s for 112 classes or 8.855E-5s per class
Exception in thread "main" com.gome.childrenmanager.InsertUserException
从例子可以看到**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.isEmpty())
suppressedExceptions = new ArrayList<>(1);
suppressedExceptions.add(exception);
}
private static Throwable[] EMPTY_THROWABLE_ARRAY;
public final synchronized Throwable[] getSuppressed() {
if (EMPTY_THROWABLE_ARRAY == null) {
EMPTY_THROWABLE_ARRAY = new Throwable[0];
}
if (suppressedExceptions == null || suppressedExceptions.isEmpty())
return EMPTY_THROWABLE_ARRAY;
else
return suppressedExceptions.toArray(EMPTY_THROWABLE_ARRAY);
}
如果try中抛出异常,在执行流程转移到方法栈上一层之前,finall语句块会执行,但是,如果在finally语句块中又抛出了一个异常,那么这个异常会覆盖掉之前抛出的异常,这有点像finally中return被覆盖了,比如下面这个例子:
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 com.gome.childrenmanager.InsertUserException.main(InsertUserException.java:39)
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 com.gome.childrenmanager.InsertUserException.main(InsertUserException.java:37)
Throwable对象提供了addSupperssed和getSupperssed方法,允许把finally语句块中产生的异常通过addSupperssed方法添加到try语句产生的异常中。
public static void main(String[] args) {
RuntimeException e1 = null;
try{
Integer.valueOf("one");
}catch (NumberFormatException e){
e1 =new RuntimeException("One", e);
throw e1;
}finally {
try{
Integer.valueOf("two");
}catch (NumberFormatException e){
RuntimeException e2 = new RuntimeException("Two", e);
e1.addSuppressed(e2);
throw e1;
}
}
}
输出:
Exception in thread "main" java.lang.RuntimeException: One
at com.gome.childrenmanager.InsertUserException.main(InsertUserException.java:35)
Suppressed: java.lang.RuntimeException: Two
at com.gome.childrenmanager.InsertUserException.main(InsertUserException.java:41)
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 com.gome.childrenmanager.InsertUserException.main(InsertUserException.java:39)
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 com.gome.childrenmanager.InsertUserException.main(InsertUserException.java:33)
Class transformation time: 0.0109227s for 113 classes or 9.666106194690266E-5s per class
5.PrintStackTrace
printStackTrace()方法分四个方面打印出当前异常信息
1)打印出当前异常的详细信息
2) 打印出异常堆栈中的栈帧信息
3) 打印出suppress异常信息
4) 递归打印出引起当前异常的异常信息
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()) {
// 打印当前异常的详细信息
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);
}
}
/**
* Print our stack trace as an enclosed exception for the specified
* stack trace.
*/
private void printEnclosedStackTrace(PrintStreamOrWriter s,
StackTraceElement[] enclosingTrace,
String caption,
String prefix,
Set<Throwable> dejaVu) {
if (dejaVu.contains(this)) {
s.println("\t[CIRCULAR REFERENCE:" + this + "]");
} else {
dejaVu.add(this);
// Compute number of frames in common between this and enclosing trace
StackTraceElement[] trace = getOurStackTrace();
int m = trace.length - 1;
int n = enclosingTrace.length - 1;
while (m >= 0 && n >=0 && trace[m].equals(enclosingTrace[n])) {
m--; n--;
}
int framesInCommon = trace.length - 1 - m;
// Print our stack trace
s.println(prefix + caption + this);
for (int i = 0; i <= m; i++)
s.println(prefix + "\tat " + trace[i]);
if (framesInCommon != 0)
s.println(prefix + "\t... " + framesInCommon + " more");
// Print suppressed exceptions, if any
for (Throwable se : getSuppressed())
se.printEnclosedStackTrace(s, trace, SUPPRESSED_CAPTION,
prefix +"\t", dejaVu);
// Print cause, if any
Throwable ourCause = getCause();
if (ourCause != null)
ourCause.printEnclosedStackTrace(s, trace, CAUSE_CAPTION, prefix, dejaVu);
}
}
参考:
https://blog.csdn.net/weixin_39787628/article/details/111118098
https://zhuanlan.zhihu.com/p/402638614