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)