一.打印棧軌跡的方法
主動調用Throwable對象的printStackTrace()=printStackTrace(System.err),printStackTrace(PrintStream),printStackTrace(PrintWriter)中的其中一個。
如果一個Exception沒有被處理,直接在main方法后面throws,程序退出前將調用異常的printStackTrace()方法,最終是Exception in thread "main" + printStackTrace()
二.棧軌跡
1、printStackTrace()
首先需要明確,這個方法并不是來自于Exception類。Exception類本身除了定義了幾個構造器之外,所有的方法都是從其父類繼承過來的。而和異常相關的方法都是從java.lang.Throwable類繼承過來的。而printStackTrace()就是其中一個。
這個方法會將Throwable對象的棧軌跡信息打印到標準錯誤輸出流上。輸出的大體樣子如下:
1
2
3
4
|
java.lang.NullPointerException at MyClass.mash(MyClass.java: 9 ) at MyClass.crunch(MyClass.java: 6 ) at MyClass.main(MyClass.java: 3 ) |
輸出的第一行是toString()方法的輸出,后面幾行的內容都是之前通過fillInStackTrace()方法保存的內容。關于這個方法,我們后面會講。
下面看一個例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public class TestPrintStackTrace { public static void f() throws Exception{ throw new Exception( "出問題啦!" ); } public static void g() throws Exception{ f(); } public static void main(String[] args) { try { g(); } catch (Exception e) { e.printStackTrace(); } } } |
這個例子的輸出如下:
1
2
3
4
|
java.lang.Exception: 出問題啦! at TestPrintStackTrace.f(TestPrintStackTrace.java:3) at TestPrintStackTrace.g(TestPrintStackTrace.java:6) at TestPrintStackTrace.main(TestPrintStackTrace.java:10) |
在這個例子中,在方法f()中拋出異常,方法g()中調用方法f(),在main方法中捕獲異常,并且打印棧軌跡信息。因此,輸出依次展示了f—>g—>main的過程。
2、getStackTrace()方法
這個方法提供了對printStackTrace()方法所打印信息的編程訪問。它會返回一個棧軌跡元素的數組。以上面的輸出為例,輸出的第2-4行每一行的內容對應一個棧軌跡元素。將這些棧軌跡元素保存在一個數組中。每個元素對應棧的一個棧幀。數組的第一個元素保存的是棧頂元素,也就是上面的f。最后一個元素保存的棧底元素。
下面是一個使用getStackTrace()訪問這些軌跡棧元素并打印輸出的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public class TestPrintStackTrace { public static void f() throws Exception{ throw new Exception( "出問題啦!" ); } public static void g() throws Exception{ f(); } public static void main(String[] args) { try { g(); } catch (Exception e) { e.printStackTrace(); System.out.println( "------------------------------" ); for (StackTraceElement elem : e.getStackTrace()) { System.out.println(elem); } } } } |
這樣的輸出和printStackTrace()的輸出基本上是一樣的,如下:
1
2
3
4
5
6
7
|
java.lang.Exception: 出問題啦! at TestPrintStackTrace.f(TestPrintStackTrace.java:3) at TestPrintStackTrace.g(TestPrintStackTrace.java:6) at TestPrintStackTrace.main(TestPrintStackTrace.java:10) TestPrintStackTrace.f(TestPrintStackTrace.java:3) TestPrintStackTrace.g(TestPrintStackTrace.java:6) TestPrintStackTrace.main(TestPrintStackTrace.java:10) |
三.fillInStackTrace方法
native fillInStackTrace()方法將返回一個Throwable對象,它是通過把當前調用棧信息填入原來那個異常對象兒建立的,所以返回的還是原來的異常。
調用此方法的那一行將成為異常新的發生地,有關原來異常發生點的信息會丟失。它的效果等價于捕獲一個異常后,重新拋出另外一種異常。兩者不同的是,fillInStackTrace后的異常還是原來的異常(只是少了棧軌跡而已);而重新拋出一個異常的話,完全跟原異常信息無關了(當然也沒有棧軌跡)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
package com.jyz.study.jdk.exception; /** * 棧軌跡 * fillInStackTrace * @author JoyoungZhang@gmail.com * */ public class FillInStackTrace { public static void main(String[] args) throws Exception { test1(); } private static void test1() throws Exception{ try { test2(); } catch (NullPointerException ex){ //1 throw (Exception)ex.fillInStackTrace(); //2 throw new Exception(); } } private static void test2(){ test3(); } private static void test3(){ throw new NullPointerException( "str is null" ); } } |
1和2的異常棧信息均如圖:
不同的是this本身的信息,控制臺第一行打印的就是this。
1的棧信息
1
2
3
|
Exception in thread "main" java.lang.NullPointerException: str is null at com.jyz.study.jdk.exception.FillInStackTrace.test1(FillInStackTrace.java:20) at com.jyz.study.jdk.exception.FillInStackTrace.main(FillInStackTrace.java:13) |
2的棧信息
1
2
3
|
Exception in thread "main" java.lang.Exception at com.jyz.study.jdk.exception.FillInStackTrace.test1(FillInStackTrace.java:21) at com.jyz.study.jdk.exception.FillInStackTrace.main(FillInStackTrace.java:13) |