Stack trace

Java Syntax Zero
Рівень 15 , Лекція 6
Відкрита

1. Отримання траси стеку

Отримання траси стеку

Мова програмування Java надає програмісту дуже багато способів отримати інформацію про те, що наразі відбувається в програмі. І це не просто слова.

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

А от у Java навіть після компіляції класи залишаються класами, методи й змінні нікуди не діваються, і програміст має багато способів отримати дані про те, що наразі відбувається в програмі.

Траса стеку

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

Список, який складається з поточного методу; методу, що його викликав; методу, що викликав цей другий метод і т. д., називається stack trace (траса стеку). Отримати його можна за допомогою команди

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

Її також можна записати у два рядки:

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

Статичний метод currentThread() класу Thread повертає посилання на об'єкт типу Thread, який містить інформацію про поточну нитку (про поточний потік виконання). Докладніше про нитки ви дізнаєтеся на 7-му й 8-му рівнях квесту Java Core.

У цього об'єкта 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

З цієї траси стеку можна зробити такий висновок:

  • Метод Thread.getStackTrace() було викликано з методу Main.test() у рядку 11 файлу Main.java
  • Метод Main.test() було викликано з методу Main.main() у рядку 5 файлу Main.java
  • Метод Main.main() ніхто не викликав — це перший метод у ланцюжку викликів.

До речі, на екрані було відображено лише частину всієї наявної інформації. Решту можна отримати безпосередньо з об'єкта StackTraceElement



2. StackTraceElement

Клас StackTraceElement, як випливає з його назви, створено для зберігання інформації щодо одного елемента траси стеку — тобто щодо одного методу з траси стеку (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 і закінчуючи методом, де сталася помилка. Там навіть є рядок, в якому було створено виняток!

Ця траса стеку для виявлення помилки зберігається всередині винятку, і її можна легко витягти звідти за допомогою методу StackTraceElement[] getStackTrace()

Приклад:

Код Примітка
try
{
   // тут може виникнути виняток
}
catch(Exception e)
{
   StackTraceElement[] methods = e.getStackTrace()
}




Захоплюємо виняток

Отримуємо з нього трасу стеку на момент виникнення помилки.

Це метод класу Throwable, а отже, усі його класи-спадкоємці (тобто взагалі всі винятки) мають метод getStackTrace(). Дуже зручно, чи не так?

Друк траси стеку для виявлення помилки

До речі, клас Throwable має ще один метод для роботи зі 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)

Коментарі (13)
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ
Stas Semenyuk_ЗСУ Рівень 18
26 квітня 2025
Чому не проходить перевірку просто через print? треба тільки через printf. Це мова про "Готовимо коктейлі". То говоримо, що реалізація може бути у кожного своя, а як до перевірки, то тільки один варіант, і шукай його по десять спроб.
IronMan57 Рівень 28
1 січня 2025
Із запуском коду останньої задачі є якісь проблеми. При запуску коду з правильним рішенням в вікно "Output" нічого не виводиться. При цьому той же самий код на моїй локальній машині компілюється й працює так, як вимагається в умові задачі, а рішення було прийняте як правильне.
Зоряна Блащук Рівень 23
9 жовтня 2024
В готуємо коктейлі при такій реалізаціі виведення не буде включати інформацію про printStackTrace(). Було б до речі цікаво поробити варіаціі -як отримати інфу лише про конкретний метод, чи отримати про всі методи...
Кирило Рівень 43
6 серпня 2024
Чому в першому завданні, якщо писати більш читаємо програма тести не проходить? Рекомендую зробити універсальніші тести
Наталья Рівень 69 Expert
2 квітня 2023
метод pop() - достает элемент, удаляет элемент, а return возвращает этот элемент
Yaroslav Tkachyk Рівень 23 Expert
13 січня 2023
Про метод pop() краще було б описати, що він видаляє останній елемент зі стеку, і при необхідності повертає його значення. 1) stack.pop() - звичайне видалення. 2) int x = stack.pop - повернення значення елементу в змінну x та його видалення зі стеку.
les_yeux_blancs Рівень 50
28 квітня 2023
Це невірне формулювання, тому що pop() (як і будь-який інший метод, що повертає будь-яке значення) завжди повертає значення, але ти можеш його проігнорувати, що значить, що ти викликаєш метод заради "сайд ефектів" (тобто він не є ідемпотентним і змінює якусь сутність і саме ця дія відносно сутності нас цікавить, наприклад, видалення елементу у цьому випадку)
9 січня 2023
Пишу комент в підтримку укр мовних самоучок!) До речі лекції стають що разу скучнішими, можливо через велику затрату енергії для зрозуміння.
Roma Chernesh Рівень 16
22 лютого 2023
ба більше "незрозумілішими"
Mykhailo Bordeichuk Рівень 35
25 грудня 2022
Доброго дня! Скажіть будь ласка, в task1414 я все зробив вірно... як мені здавалось але задача не пройшла перевірку я підглянув у відповідь і побачив те що я не вивів на екран змінну OUTPUT_FORMAT. Скажіть а навіщо її виводити? ну в завданні сказано вивести інфу про кожен елемент масиву, але дане поле не є елементом масиву. Відповідно навіщо її виводити? Поясніть бо я чомусь не розумію.
Grimnir Рівень 11
29 грудня 2022
Ну мені здалося очевидним використання String.format. Правда стопорнувся з самим виведенням, бо на автоматі прописав прінтлн замість прінтф))
FAUST_ua Рівень 29
8 вересня 2022
Дивно що немає жодних коментарів... Я вирішував першу задачу методом "научного тику" всього лише годинки за 2, не більше. Чудово все зрозумів до цього з лекції мабудь!
Zoriana Рівень 30
16 вересня 2022
Нас тут просто дуже мало, на українській частині сайту. Всього цю задачу вирішила 161 людина.....