Что такое исключение
Чему вы научитесь:
- читать сообщение об исключении и находить по трассировке стека строку, где случилась ошибка;
- ловить исключения в
tryиcatchи выполнять обязательный код вfinally; - отличать проверяемые исключения от непроверяемых и бросать свои через
throw; - закрывать ресурсы автоматически через try-with-resources.
Представьте офисное здание, где работа спускается сверху вниз. Директор на верхнем этаже поручает задачу отделу этажом ниже. Отдел передаёт часть работы в мастерскую ещё ниже и ждёт, пока та вернёт результат.
Вдруг в мастерской срабатывает пожарная сигнализация. Если там есть человек, который знает, что делать, он отвечает на тревогу, и работа идёт дальше. Если такого нет, мастерская бросает работу, а сигнал звучит этажом выше — у того, кто её поручил. Так этаж за этажом, вверх по лестнице, пока кто-нибудь не ответит. Не ответил даже директор — здание эвакуируют целиком.
Программа на Java устроена так же. Метод main поручает работу другим методам, а те вызывают следующие. Когда метод не может закончить работу, срабатывает сигнализация — исключение. Вы уже встречали такие тревоги:
ArithmeticException— целое деление на ноль;ArrayIndexOutOfBoundsException— индекс за границей массива;NullPointerException— обращение к полю или методу черезnull;ClassCastException— приведение к классу, которым объект не является;NumberFormatException—Integer.parseIntполучил строку, которая не целое число.
Каждый раз программа компилировалась, а при запуске останавливалась: на тревогу никто не отвечал, и «эвакуировали» всю программу. В этой теме вы научитесь отвечать на тревогу. Но сначала разберём, что именно сообщает Java, когда программа останавливается.
Исключение — это объект
Возьмём программу из трёх этажей: main вызывает report, а report — метод average, который делит сумму на количество.
public class Main {
static int average(int sum, int n) {
return sum / n;
}
static void report(int sum, int n) {
System.out.println("Average: " + average(sum, n));
}
public static void main(String[] args) {
report(10, 2);
report(10, 0);
System.out.println("Done");
}
}Запустим её:
Average: 5
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Main.average(Main.java:3)
at Main.report(Main.java:7)
at Main.main(Main.java:12)Давайте разберём, что произошло:
report(10, 2)вызываетaverage(10, 2), получает5и печатаетAverage: 5.report(10, 0)вызываетaverage(10, 0), и у деления10 / 0нет результата.- JVM создаёт объект класса
ArithmeticExceptionи бросает его. Методaverageзавершается сразу, безreturn. reportтак и не печатает свою строку, аmainне доходит доDone: программа останавливается.
Исключение — больше, чем текст на экране: это объект. У него есть класс, например ArithmeticException, и сообщение — здесь / by zero. Бросить исключение — значит создать такой объект и прервать обычный ход программы.
Первая строка: что случилось
Первая строка сообщения отвечает на вопрос «что случилось»:
Exception in thread "main"— исключение случилось в главном потоке программы, в том, что выполняетmain;java.lang.ArithmeticException— полное имя класса исключения: пакетjava.langи сам класс;/ by zero— сообщение с подробностями: делили на ноль.
Часто всё понятно уже по имени класса: ArithmeticException — ошибка в арифметике, NullPointerException — где-то оказался null.
Трассировка стека: где случилось
Строки, которые начинаются с at, называют трассировкой стека. Каждая из них — один незавершённый вызов из стека вызовов, о котором шла речь в теме о методах. Читайте трассировку сверху вниз:
at Main.average(Main.java:3)— классMain, методaverage, файлMain.java, строка 3. Здесь исключение бросили.at Main.report(Main.java:7)—averageвызвали из методаreport, из строки 7.at Main.main(Main.java:12)— аreportвызвали изmain, из строки 12.
Трассировка похожа на журнал пожарной сигнализации. Первым в нём записан этаж, где сработал датчик, ниже — каждый этаж на пути вверх, последним — директор main.
На собеседовании иногда показывают трассировку и спрашивают, где ошибка. Начинайте с первой строки at: там исключение бросили, а строки ниже показывают, кто кого вызвал.
Здесь картинка со зданием неточна. Настоящая сирена слышна во всём доме, а исключение идёт только по цепочке незавершённых вызовов. Первый вызов report(10, 2) к этому моменту уже вернулся, поэтому в трассировке его нет.
Частая ошибка
Трассировка начинается не с вашего кода, и новички ищут ошибку внутри самой Java:
String text = "12a";
int n = Integer.parseInt(text); // ✗
System.out.println(n + 1);Exception in thread "main" java.lang.NumberFormatException: For input string: "12a"
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)
at java.base/java.lang.Integer.parseInt(Integer.java:565)
at java.base/java.lang.Integer.parseInt(Integer.java:662)
at Main.main(Main.java:4)Первые три строки at ведут внутрь стандартной библиотеки: на это указывает приставка java.base/. Ошибки в библиотеке нет. Метод parseInt честно сообщил, что строка "12a" — не целое число, и бросил исключение изнутри себя.
Ищите первую строку со своим классом: at Main.main(Main.java:4). Это ваш вызов parseInt — в файле перед этим фрагментом стоят ещё две строки, заголовки класса и метода main. Исправлять нужно именно вызов: передавать в parseInt только целое число.
Главное
- Исключение — объект с классом и сообщением; JVM бросает его, когда программа не может продолжить работу.
- Брошенное исключение завершает метод, а за ним каждый вызвавший метод; если на тревогу никто не ответил, программа останавливается.
- Первая строка сообщения называет класс исключения и сообщение, а строки
at— это трассировка стека. - Трассировку читают сверху: первая строка
at— место, где бросили исключение, а свою ошибку ищут в первой строкеatсо своим классом.