1.獲取堆棧跟踪

獲取堆棧跟踪

Java 編程語言為程序員提供了許多方法來獲取有關程序中發生的事情的信息。而不僅僅是文字。

例如,C++程序編譯後變成了一個充滿機器代碼的大文件,而程序員在運行時可用的只是包含當前正在執行的機器代碼的內存塊的地址。不是很多,讓我們說。

但是對於 Java 來說,即使在程序被編譯之後,類仍然是類,方法和變量並沒有消失,程序員有很多方法可以獲取有關程序中發生的事情的信息。

堆棧跟踪

例如,在程序執行的某個時刻,您可以找出當前正在執行的方法的類和名稱。而不僅僅是一種方法——您可以獲得有關從當前方法返回到方法的整個方法調用鏈的信息main()

由當前方法、調用它的方法以及調用該方法的方法等組成的列表稱為堆棧跟踪。你可以用這個語句得到它:

StackTraceElement[] methods = Thread.currentThread().getStackTrace();

你也可以把它寫成兩行:

Thread current = Thread.currentThread();
StackTraceElement[] methods = current.getStackTrace();

該類的靜態currentThread()方法Thread返回一個對象的引用Thread,該對象包含當前線程的信息,即當前執行的線程。您將在Java Core任務的第 17 級和第 18 級中了解有關線程的更多信息。

這個Thread對像有一個getStackTrace()方法,它返回一個對像數組StackTraceElement,每個對像都包含一個方法的信息。綜合起來,所有這些元素形成了堆棧跟踪

例子:

代碼
public class Main
{
   public static void main(String[] args)
   {
      test();
   }

   public static void test()
   {
      Thread current = Thread.currentThread();
      StackTraceElement[] methods = current.getStackTrace();

      for(var info: methods)
         System.out.println(info);
   }
}
控制台輸出
java.base/java.lang.Thread.getStackTrace(Thread.java:1606)
Main.test(Main.java:11)
Main.main(Main.java:5)

正如我們在示例的控制台輸出中看到的那樣,該getStackTrace()方法返回了一個包含三個元素的數組:

  • getStackTrace()Thread類的方法
  • test()Main類的方法
  • main()Main類的方法

從這個堆棧跟踪中,我們可以得出結論:

  • 該方法被Main.java文件第11行的方法Thread.getStackTrace()調用Main.test()
  • 該方法被Main.java文件第5行的方法Main.test()調用Main.main()
  • 沒有人調用該Main.main()方法——這是調用鏈中的第一個方法。

順便說一下,屏幕上只顯示了一些可用的信息。StackTraceElement其他一切都可以直接從對像中獲取



2.StackTraceElement

顧名思義,StackTraceElement創建該類是為了存儲有關堆棧跟踪元素的信息,即stack trace.

此類具有以下實例方法:

方法 描述
String getClassName()
返回類的名稱
String getMethodName()
返回方法的名稱
String getFileName()
返回文件名(一個文件可以包含多個類)
int getLineNumber()
返回調用該方法的文件中的行號
String getModuleName()
返回模塊的名稱(可以是null
String getModuleVersion()
返回模塊的版本(可以是null

它們可以幫助您獲得有關當前調用堆棧的更完整信息:

代碼 控制台輸出 筆記
public class Main
{
   public static void main(String[] args)
   {
      test();
   }

   public static void test()
   {
      Thread current = Thread.currentThread();
      StackTraceElement[] methods = current.getStackTrace();

      for(StackTraceElement info: methods)
      {
         System.out.println(info.getClassName());
         System.out.println(info.getMethodName());

         System.out.println(info.getFileName());
         System.out.println(info.getLineNumber());

         System.out.println(info.getModuleName());
         System.out.println(info.getModuleVersion());
         System.out.println();
      }
   }
}
java.lang.Thread
getStackTrace
Thread.java
1606
java.base
11.0.2

Main
test
Main.java
11
null
null

Main
main
Main.java
5
null
null
類名方法

文件
名行號
模塊名
模塊版本

類名
方法名
文件名
行號
模塊名
模塊版本

類名
方法名
文件名
行號
模塊名
模塊版本


3.堆棧

您已經知道什麼是堆棧跟踪,但什麼是堆棧(Stack 類)?

堆棧是一種數據結構,您可以向其中添加元素,也可以從中檢索元素。在這樣做時,您只能從末尾獲取元素:首先獲取最後添加的元素,然後是倒數第二個添加的元素,依此類推。

名稱堆棧本身暗示了這種行為,就像您將如何與一堆文件進行交互一樣。如果將工作表 1、2 和 3 堆疊在一起,則必須以相反的順序檢索它們:首先是第三個工作表,然後是第二個工作表,然後才是第一個。

Java 甚至有一個具有相同名稱和行為的特殊 Stack 集合類。ArrayList這個類與和共享很多行為LinkedList。但它也有實現堆棧行為的方法:

方法 描述
T push(T obj)
obj元素添加到棧頂
T pop()
從棧頂取出元素(棧深遞減)
T peek()
返回棧頂的項目(棧不會改變)
boolean empty()
檢查集合是否為空
int search(Object obj)
在集合中搜索一個對象並返回它的index

例子:

代碼 棧內容(棧頂在右邊)
Stack<Integer> stack = new Stack<Integer>();
stack.push(1);
stack.push(2);
stack.push(3);
int x = stack.pop();
stack.push(4);
int y = stack.peek();
stack.pop();
stack.pop();

[1]
[1, 2]
[1, 2, 3]
[1, 2]
[1, 2, 4]
[1, 2, 4]
[1, 2]
[1]

堆棧在編程中經常使用。所以這是一個有用的集合。



4. 在異常處理期間顯示堆棧跟踪

為什麼方法調用列表稱為堆棧跟踪?因為如果您將方法列表想像成一疊寫有方法名稱的紙,那麼當您調用下一個方法時,您就將一張寫有該方法名稱的紙添加到堆棧中。下一張紙放在上面,依此類推。

當方法結束時,堆棧頂部的工作表將被移除。如果不移除其上方的所有工作表,則無法從堆棧中間移除工作表。同樣,您不能在調用鏈中間終止一個方法而不終止它調用的所有方法。

例外情況

堆棧的另一個有趣用途是在異常處理期間。

當程序中發生錯誤並拋出異常時該異常包含當前堆棧跟踪一個由一系列方法組成的數組,這些方法從 main 方法開始,以發生錯誤的方法結束。甚至還有拋出異常的那一行!

此堆棧跟踪存儲在異常中,可以使用以下方法從中輕鬆檢索:StackTraceElement[] getStackTrace()

例子:

代碼 筆記
try
{
   // An exception may occur here
}
catch(Exception e)
{
   StackTraceElement[] methods = e.getStackTrace()
}




Catch the exception

獲取錯誤發生時存在的堆棧跟踪。

這是該類的一個方法Throwable,所以它的所有後代(即所有異常)都有該getStackTrace()方法。超級方便吧?

顯示異常的堆棧跟踪

順便說一下,該類Throwable還有另一種處理堆棧跟踪的方法,一種顯示存儲在異常中的所有堆棧跟踪信息的方法。它被稱為printStackTrace()

非常方便,您可以在任何異常時調用它。

例子:

代碼
try
{
   // An exception may occur here
}
catch(Exception e)
{
   e.printStackTrace();
}
控制台輸出
java.base/java.lang.Thread.getStackTrace(Thread.java:1606)
Main.test(Main.java:11)
Main.main(Main.java:5)