Виды ошибок
- Ошибка компиляции — код не соответствует синтаксису или типам.
javacне создает.class. - Ошибка выполнения — программа запустилась, но выбросила исключение.
- Логическая ошибка — программа работает, но результат неверный. Сложнее всего находить.
Чтение stack trace
Пример программы:
public class Demo { static int parse(String s) { return Integer.parseInt(s); } static int process(String input) { return parse(input) * 2; } public static void main(String[] args) { System.out.println(process("12")); System.out.println(process("abc")); }}Вывод:
24Exception in thread "main" java.lang.NumberFormatException: For input string: "abc" at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67) at java.base/java.lang.Integer.parseInt(Integer.java:661) at java.base/java.lang.Integer.parseInt(Integer.java:777) at Demo.parse(Demo.java:3) at Demo.process(Demo.java:7) at Demo.main(Demo.java:12)Как читать:
- Первая строка — тип исключения и сообщение:
NumberFormatException: For input string: "abc". - Строки
at ...— стек вызовов, сверху вниз от места ошибки к точке входа. - Строки из
java.base/...— внутренности JDK. Искать причину нужно в первой строке со своим классом:Demo.parse(Demo.java:3). - Дальше видно, кто вызвал
parse:processна 7 строке, а того вызвалmainна 12 строке.
Алгоритм: прочитать тип и сообщение, найти первую свою строку, открыть файл на этой строке, понять, какое значение оказалось неожиданным.
Блок Caused by
Когда исключение обернуто в другое, в конце появляется причина:
Exception in thread "main" java.lang.IllegalStateException: Не удалось загрузить настройки at Config.load(Config.java:15) at Main.main(Main.java:5)Caused by: java.io.FileNotFoundException: settings.json (No such file or directory) at java.base/java.io.FileInputStream.open0(Native Method) at Config.load(Config.java:12) ... 1 moreНастоящая причина обычно в последнем блоке Caused by.
Частые исключения и их причины
| Исключение | Типичная причина |
|---|---|
NullPointerException | вызов метода у null |
ArrayIndexOutOfBoundsException | индекс вне границ массива |
IndexOutOfBoundsException | индекс вне границ списка |
ClassCastException | неверное приведение типа |
NumberFormatException | строка не является числом |
ConcurrentModificationException | изменение коллекции во время перебора |
StackOverflowError | бесконечная рекурсия |
OutOfMemoryError | утечка памяти или слишком большие данные |
NoClassDefFoundError | не хватает класса или библиотеки в classpath |
UnsupportedOperationException | изменение неизменяемой коллекции |
Современные версии Java выдают подробные сообщения для NullPointerException:
Cannot invoke "String.length()" because "<local1>" is nullОтладка выводом
Самый простой способ — напечатать промежуточные значения:
public class PrintDebug { static int sumEven(int[] data) { int sum = 0; for (int i = 0; i < data.length; i++) { System.out.println("i=" + i + ", value=" + data[i] + ", sum=" + sum); if (data[i] % 2 == 0) { sum += data[i]; } } return sum; } public static void main(String[] args) { System.out.println(sumEven(new int[]{1, 2, 3, 4})); }}Метод быстрый, но не оставляйте такой вывод в готовом коде. Для сообщений об ошибках используйте System.err.println.
Отладчик
Отладчик позволяет остановить программу в нужной точке, посмотреть значения переменных и выполнять код по шагам.
Основные понятия
- Breakpoint — точка останова. Выполнение приостанавливается перед этой строкой.
- Step Over — выполнить текущую строку целиком, не заходя в методы.
- Step Into — зайти внутрь вызываемого метода.
- Step Out — выполнить до конца текущего метода и вернуться.
- Resume — продолжить до следующей точки останова.
- Watch — выражение, значение которого отслеживается при каждом шаге.
- Call stack — текущая цепочка вызовов.
IntelliJ IDEA
- Кликните слева от номера строки, чтобы поставить точку останова (красный кружок).
- Запустите программу в режиме Debug: значок жука или
Shift+F9(macOS:Ctrl+D). - На панели Debug смотрите переменные, стек и добавляйте Watch.
- Шаги:
F8— Step Over,F7— Step Into,Shift+F8— Step Out,F9— Resume.
Дополнительные возможности:
- Условная точка останова: правая кнопка по красному кружку → Condition, например
i == 5. - Evaluate Expression:
Alt+F8— вычислить выражение на лету. - Изменение значения: правая кнопка по переменной → Set Value.
- Точка останова на исключении: диалог Breakpoints → Java Exception Breakpoints.
Eclipse
- Точка останова: двойной клик на левом поле редактора.
- Запуск: Debug As → Java Application, или
F11. - Шаги:
F6— Step Over,F5— Step Into,F7— Step Return,F8— Resume. - Перспектива Debug показывает переменные, стек и точки останова.
VS Code
- Точка останова: клик слева от номера строки.
- Запуск:
F5или кнопка Debug над методомmain. - Шаги:
F10— Step Over,F11— Step Into,Shift+F11— Step Out,F5— Continue. - Панель Run and Debug показывает Variables, Watch, Call Stack.
Пример отладки
В программе ошибка: должна считаться сумма четных чисел, а результат неверный.
public class Buggy { static int sumEven(int[] data) { int sum = 0; for (int i = 1; i < data.length; i++) { if (data[i] % 2 == 0) { sum += data[i]; } } return sum; } public static void main(String[] args) { System.out.println(sumEven(new int[]{2, 4, 5, 6})); }}Ожидается 12, выводится 10. Поставьте точку останова на строку if и пройдите цикл по шагам. Станет видно, что i начинается с 1 и элемент data[0] пропускается. Исправление: int i = 0.
Стратегия поиска ошибки:
- Воспроизведите ошибку стабильно.
- Сузьте область: на каком участке данные еще верные, а где уже нет.
- Сформулируйте гипотезу и проверьте ее отладчиком.
- После исправления напишите тест, который ловит эту ошибку.
Логирование
Для настоящих приложений вместо System.out используют логгер. В стандартной библиотеке есть java.util.logging:
import java.util.logging.Level;import java.util.logging.Logger;public class LogDemo { private static final Logger LOG = Logger.getLogger(LogDemo.class.getName()); public static void main(String[] args) { LOG.info("Приложение запущено"); LOG.warning("Мало свободного места"); try { Integer.parseInt("xyz"); } catch (NumberFormatException e) { LOG.log(Level.SEVERE, "Ошибка разбора числа", e); } }}Уровни по возрастанию важности: FINEST, FINER, FINE, CONFIG, INFO, WARNING, SEVERE. В проектах чаще применяют SLF4J с Logback, подключаемые через Maven или Gradle.
Утверждения assert
public class AssertDemo { static double average(int[] data) { assert data.length > 0 : "Массив не должен быть пустым"; double sum = 0; for (int n : data) { sum += n; } return sum / data.length; } public static void main(String[] args) { System.out.println(average(new int[]{1, 2, 3})); }}Проверки assert по умолчанию выключены. Включение: java -ea AssertDemo. Не используйте assert для проверки пользовательского ввода.
Консольный отладчик jdb
javac -g Buggy.javajdb BuggyФлаг -g добавляет отладочную информацию. Основные команды jdb:
stop at Buggy:5runlocalsprint inextstepcontquitДиагностика работающего процесса
Инструменты JDK:
jpsjstack <pid>jcmd <pid> Thread.printjcmd <pid> GC.heap_infojmap -histo <pid>jps— список запущенных Java-процессов;jstackиjcmd Thread.print— снимок стеков всех потоков, помогает при зависаниях и взаимоблокировках;jmap -histo— гистограмма объектов в памяти, помогает искать утечки.
Для профилирования есть JDK Flight Recorder и VisualVM.
Советы
- Читайте сообщение об ошибке полностью, ответ часто в нем.
- Ищите первую строку стека, относящуюся к вашему коду.
- Меняйте одно за раз.
- Не угадывайте: проверяйте гипотезы отладчиком или логами.
- Объясните проблему вслух или на бумаге: этот прием часто сам подсказывает решение.
