User Alukard
Alukard
36 уровень
London

Stack Trace и с чем его едят

Статья из группы Random
В этой статье вы узнаете и поймете, как работает такое явление в Java, как StackTrace, так же известное как "Трассировка стека вызовов". Эта информация была структурирована для новичков, столкнувшихся с этим понятием в начале девятого уровня Java Syntax. Я думаю все из вас, хоть раз, но встречали похожие ошибки при работе в вашем IDE, независимо от того будь это Idea, Eclipse или что-то другое.
Exception in thread "main" java.lang.ArithmeticException
	at com.example.task01.Test.division(Test.java:10)
	at com.example.task01.Test.main(Test.java:6)
Это, как вы уже догадались и есть наша трассировка. Но не спешите паниковать, сейчас мы с вами разложим данный пример на пальцах. Для начала необходимо понять тот факт, что StackTrace работает как Стэк и это видно из его названия. На этом месте мы остановимся чуть поподробнее. Принцип работы коллекции Stack На восьмом уровне вы уже познакомились с коллекциями и знаете что они делятся на три группы Set — множество, List — список, Map — словарь (или карта). По мнению JavaRush (c). Наш Stack входит в группу List. Принцип его работы можно описать как LIFO, что расшифровывается как Last In First Out(Последний пришел, первый ушел). А именно это такой список похожий на стопку книг, чтобы взять элемент который мы положили в Stack первым, нам необходимо сначала извлечь все элементы которые мы добавили в наш список после. Как это указано на картинке выше в отличии например от обычного списка ArrayList где мы можем получить любой элемент из списка по индексу. Еще раз для закрепления. Получение элемента из Стэка возможно только с конца! В то время как первый добавленный в него элемент находится в начале(или на дне как удобнее). Вот какие методы имеет наш Stack Object push() - Добавляет элемент в верх стека. Object pop() - Возвращает элемент, находящийся в верхней части стэка, удаляя его в процессе. Object peek() - Возвращает элемент, находящийся в верхней части стэка, но не удаляет его. int search() - Ищет элемент в стеке. Если найден, возвращается его смещение от вершины стека. В противном случае возвращается -1. boolean empty() - Проверяет, является ли стек пустым. Возвращает true, если стек пустой. Возвращает false, если стек содержит элементы. Так для чего же в Java нужен StackTrace построеный на принципах работы Stack? Давайте разберем пример ошибки ниже, которая возникла в процессе выполнения такой вот простой программы.
public class Test {

    public static void main(String[] args) {
        System.out.println(convertStringToInt(null));
    }

    public static int convertStringToInt(String s) {
        int x = Integer.parseInt(s);
        return x;
    }
}
У нас есть класс Test с двумя методами. Всем привычный main и convertStringToInt логика которого заключается в конвертировании и возврате полученной извне(а именно из метода main) строки в целочисленное число типа int. Как вы видите мы намеренно передали вместо строки с какой-нибудь цифрой, параметр null. Данный параметр наш метод не смог правильно обработать и вызвал ошибку NumberFormatException. Как вы знаете программа начинает отрабатывать свою работу из метода main и в этот момент она создает новый Стэк с названием StackTrace куда кладет текущее значение ее работы под номером 1, далее мы переходим в метод convertStringToInt и программа опять заносит параметры нашего нахождения в созданный ранее StackTrace под номером 2, далее вызывается не видимый нашему глазу метод parseInt находящийся в классе Integer и это уже будет элемент под номером 3 нашего StackTrace, в этом методе будет еще один внутренний вызов добавленный в StackTrace под номером 4 для проверки элемента на null который и приведет к ошибке. Программе необходимо вывести нашу ошибку с указанием всей цепочки наших переходов до момента возникновения ошибки. Тут то ей и приходит на помощь ранее созданный StackTrace с данными наших переходов.
Exception in thread "main" java.lang.NumberFormatException: null
	at java.base/java.lang.Integer.parseInt(Integer.java:614)
	at java.base/java.lang.Integer.parseInt(Integer.java:770)
	at com.example.task01.Test.convertStringToInt(Solution.java:10)
	at com.example.task01.Test.main(Solution.java:6)
До возникновения ошибки, программа шла вглубь методов, но как только возникла ошибка, все начинает происходить в обратном порядке. Печатается строка с описанием проблемы(№1 на примере), далее берется последнее (и находящееся на вершине) добавленное значение в наш Стэк оно было под номером четыре и печатается в консоль(№2 на примере) и мы видим что проблема возникла в классе Integer на 614 строке кода и вызвала эту строку, строка 770 метода parseInt того же класса(№3 на примере) которая при добавлении в Стэк была под номером три и этот метод класса Integer все еще не видимый нам был вызван уже нашим методом convertStringToInt располагающемся на 10 строке нашей программы(№4 на примере, а при добавлении он был вторым), а его в свою очередь вызвал main на 6 строке(№5 на примере, а при добавлении соответственно первый). Вот так вот, складируя в Стек шаг за шагом наши вызываемые методы мы смогли вернуться обратно в main параллельно печатая информацию что именно привело нас к ошибке. Но StackTrace это не только работа с ошибками, он позволяет получить нам кучу интересной информации о процессе работы нашего приложения. Давайте разберем еще один популярный пример в комментариях к основной лекции 9го уровня. У нас есть код и к нему сразу прикреплю картинку визуализирующую процесс работы программы:
public class Test {
    public static void main(String[] args) {
        method1();
        method2();
    }
    public static void method1() {
        //не вызывает ничего
    }
    public static void method2() {
        method3();
        method4();
    }
    public static void method3() {
        //не вызывает ничего
    }
    public static void method4() {
        method5();
    }
    public static void method5() {
        StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
        for (StackTraceElement element:stackTraceElements) {
            System.out.println(element.getMethodName());
        }
    }
}
Stack Trace и с чем его едят - 2 Тут наша программа безошибочно выполняет свою работу и заканчивается. Вот что мы увидим в выводе консоли:
getStackTrace
method5
method4
method2
main

Process finished with exit code 0
Как у нас получился такой вывод и что же произошло в пятом методе начиная с 20й строки? Боюсь самое лучше что я смогу сделать это добавить самое популярное объяснение(в сокращении) юзера Кирилла из комментариев к лекции. Обратимся к строчке по созданию StackTrace и разберем ее поэлементно:
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
StackTraceElement[] - указание на тип массива(На ранних уровнях вы уже проходили массивы типа int[], String[], вот тут тоже самое). stackTraceElements - имя массива, может быть любым с учетом общих правил наименования на работу эту не влияет. Thread.currentThread() - получение ссылки на текущий поток, в котором выполняются методы, которые мы хотим отследить(пока это не важно, подробнее потоки вы будете разбирать на 16 уровне в квесте Java Core) getStackTrace() - получаем весь Стэк вызываемых методов(Это обычный геттер для StackTrace) Теперь посмотрим, чем нам может быть полезен созданный массив. Мы понимаем, что в массиве хранится инфа о выполненных методах.(с) И для этого в 21й строке мы запускаем модифицированный цикл for под названием forEach(кстати кто еще не изучил этот цикл, советую почитать о нём) и выводим данные из массива в консоль, а именно информацию какие методы выполнялись в процессе работы посредством конструкции element.getMethodName(). Внимание как мы видим нулевым элементом массива у нас оказался сам getStackTrace() соответственно так как в момент получения массива данных он был последним методом что выполнился и тем самым оказавшись на верхушке Стэка, а помня про нашу конструкцию "Последний пришел, первый ушел" сразу же первым добавляется в массив под нулевым элементом. Вот что еще мы можем получить из StackTraceElement: String getClassName() - Возвращает имя класса. String getMethodName() - Возвращает имя метода. String getFileName() - Возвращает имя файла (в одном файле может быть много классов). String getModuleName() - Возвращает имя модуля (может быть null). String getModuleVersion() - Возвращает версию модуля (может быть null). int getLineNumber() - Возвращает номер строки в файле, в которой был вызов метода. Теперь, когда вы поняли общий принцип работы, советую вам самим опробовать разные методы StackTrace в вашей Ide. Даже если вы не совсем всё усвоили, продолжайте обучение и мозаика сложится так же как сложилась у меня в данном вопросе. Желаю вам всем успехов! P.s. Если вам понравился данный материал, пожалуйста поддержите лайком. Вам не трудно, мне приятно. Спасибо и увидимся на 41 уровне ;)
Комментарии (64)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Alexander Oparin Уровень 18, Санкт-Петербург, Russian Federation
3 декабря 2021
у вас в примере как будто бы перепутаны номера строк. main - это строка 3, convertStringToInt - это строка 7
Hugon Уровень 17, Москва, Россия
22 ноября 2021
Спасибо, Алукард!
Alexander Anopov Уровень 16, Москва
4 ноября 2021
Спасибо тебе, добрый человек)
Phoenix_LVS Уровень 17, Украина
3 ноября 2021
Очень понравилась статья. С Вашим примером немного поэкспериментировал в idea и со многим разобрался. Спасибо за труд.
Vorobety Уровень 34, Москва, Russian Federation
28 октября 2021
Спасибо за такую грамотную и полезную статью! Признаюсь честно, в лекции с Java Syntax в целом понял, но вот так разбить на составные части и рассказать никому не смог бы. Ваша же статья помогла разбить весь процесс до мельчайших процессов. Btw, Ван Хельсинг всегда крутой, где бы он ни был, так что меньшего не ожидалось😂
Игорь Уровень 33, Москва , Россия
6 сентября 2021
Благодарю, кратко и понятно. Стоит перечитывать статью почаще, чтобы запомнилось.
Serhio Gonsales Уровень 32, Москва
3 сентября 2021
Просто супер! Как доп статья по теме просто отлично. Спасибо!
Leonid Tomskiy Уровень 29, Томск
24 августа 2021
Все очень скомканно. В одних местах Стэк, в других стек, уж определитесь.
Oleg Chumin Уровень 47, Россия
2 августа 2021
Почему нельзя было это написать в основной статье про StackTrace. Все что есть в JAVA, очень просто разобрать по элементам, как в этой статье, но нелогично вываливают какую-то инфу и сиди втыкай без данных. У курса методология преподавания от простого к сложному хромает сильно.
Андрей Уровень 23, Москва
20 мая 2021
Максимально понятная статья, автору респект!