1. Получение стек-трейса

Получение стек-трейса

В языке программирования Java у программиста есть очень много способов получить информацию о том, что сейчас происходит в программе. И это не просто слова.

Например, программы на языке C++ после компиляции превращаются в один большой файл машинного кода и все, что во время выполнения доступно программисту, — это адрес куска памяти, который содержит машинный код, который сейчас выполняется. Не густо, скажем так.

В Java же, даже после компиляции, классы остаются классами, методы и переменные никуда не деваются, и у программиста есть много способов получить данные о том, что сейчас происходит в программе.

Стек-трейс

Например, в любой момент работы программы можно узнать класс и имя метода, который сейчас выполняется. И даже не одного метода, а получить информацию о всей цепочке вызовов методов от текущего метода до метода main().

Список, состоящий из текущего метода, метода, который его вызвал, его вызвавшего метода и т.д., называется stack trace. Получить его можно с помощью команды:

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

Можно записать ее и в две строки:

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

Статический метод currentThread() класса Thread возвращает ссылку на объект типа Thread, который содержит информацию о текущей нити (о текущем потоке выполнения). Подробнее о нитях вы узнаете в 17 и 18 уровнях квеста Java Core.

У этого объекта Thread есть метод getStackTrace(), который возвращает массив элементов StackTraceElement, каждый из которых содержит информацию об одном методе. Все элементы вместе и образуют stack trace.

Пример:

Код
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

Из этого стек-трейса можно сделать вывод, что:

  • Метод Thread.getStackTrace() был вызван методом Main.test() в строке 11 файла Main.java
  • Метод Main.test() был вызван методом Main.main() в строке 5 файла Main.java
  • Метод Main.main() никто не вызывал — это первый метод в цепочке вызовов.

Кстати, на экране отобразилась только часть всей имеющийся информации. Все остальное можно получить прямо из объекта StackTraceElement



2. StackTraceElement

Класс StackTraceElement, как следует из его названия, создан для того, чтобы хранить информацию по одному элементу stack trace — т.е. по одному методу из StackTrace.

У объектов этого класса есть такие методы:

Метод Описание
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 Trace вы уже знаете, а что же такое сам Stack (Стек)?

Стек — это структура хранения данных, в которую можно добавлять элементы и из которой можно забирать элементы. Причем брать элементы можно только с конца: сначала последний добавленный, потом — предпоследний, и т.д.

Само название 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. Вывод стек-трейса при обработке ошибок

Почему же список вызовов методов назвали StackTrace? Да потому, что если представить список методов в виде стопки листов с именами методов, при вызове очередного метода на эту стопку кладется лист с именем метода, на него — следующий, и т.д.

Когда метод завершается, лист с верха стопки удаляется. Нельзя удалить лист из середины стопки, не удалив все листы, лежащие в нем — нельзя прекратить работу метода в цепочке вызовов, не завершив все методы, вызванные им.

Исключения

Еще одно интересное применение стека — обработка исключений.

Когда в программе происходит ошибка и создается исключение, в него записывается текущий stack trace: массив, состоящий из списка методов начиная с метода main и заканчивая методом, где произошла ошибка. Там даже есть строка, в которой было создано исключение!

Этот stack trace ошибки хранится внутри исключения и может быть легко извлечен из нее с помощью метода: StackTraceElement[] getStackTrace()

Пример:

Код Примечание
try
{
   // тут может возникнуть исключение
}
catch(Exception e)
{
   StackTraceElement[] methods = e.getStackTrace()
}




Захватываем исключение

Получаем из него стек-трейс в момент возникновения ошибки.

Это метод класса Throwable, а значит, все его классы-наследники (т.е. вообще все исключения), имеют метод getStackTrace(). Очень удобно, не так ли?

Печать стек-трейса ошибки

Кстати, у класса Throwable есть еще один метод для работы со stack-trace: он выводит в консоль всю информацию по stack trace, который хранится внутри исключения. Он так и называется printStackTrace().

Вызвать его можно у любого исключения, что очень удобно.

Пример:

Код
try
{
   // тут может возникнуть исключение
}
catch(Exception e)
{
   e.printStackTrace();
}
Вывод на экран
java.base/java.lang.Thread.getStackTrace(Thread.java:1606)
Main.test(Main.java:11)
Main.main(Main.java:5)