Исключения в Java. Часть 1
Разбираемся, что такое исключения, зачем они нужны и как с ними работать.
vlada_maestro / shutterstock
Из этой статьи вы узнаете:
- что такое исключения (Exceptions);
- как они возникают и чем отличаются от ошибок (Errors);
- зачем нужна конструкция try-catch;
- как разобраться в полученном исключении
- и как вызвать исключение самому.
Код вашей программы исправно компилируется и запускается, только вот вместо желанного результата вы видите непонятный текст. Строчки его будто кричат на вас, аж побагровели.
За примером далеко ходить не надо: сделаем то, что нам запрещали ещё в школе, — поделим на ноль.
А получим вот что:
Это и есть исключение.
«Исключение» — сокращение от слов «исключительный случай». Это ситуация, в которой программа не может продолжить работу или её работа становится бессмысленной. Причём речь не только о нештатных ситуациях — исключения бывают и намеренными, такие разработчик вызывает сам.
Это интересно. Исключения в Java появились уже в первой версии языка. А вот в языках, где их нет, вместо них возвращают коды ошибок.
Иерархия исключений и ошибки
У всех классов исключений есть общий класс-предок Throwable, от него наследуются классы Error и Exception, базовые для всех прочих.
Что такое ошибки (Errors)
Error — это критические условия, в которых работа программы должна быть завершена. Например, когда при выполнении программы закончилась память, произошёл сбой в системе или виртуальной машине. Не будем задерживаться на этой ветке, поскольку документация Java говорит:
Error is the superclass of all the exceptions from which ordinary programs are not ordinarily expected to recover.
Что в переводе означает: ошибки (Error) — это такие исключительные ситуации, в которых восстанавливать работу программы не предполагается.
То есть это проблемы, которые нельзя (недопустимо) исправлять на ходу. Всё, что нам остаётся, — извиниться перед пользователем и впредь писать программы, где возникнет меньше подобных ситуаций. Например, не допускать такой глубокой рекурсии, как в коде ниже:
При работе этого метода у нас возникнет ошибка: Exception in thread «main» java.lang.StackOverflowError — стек вызовов переполнился, так как мы не указали условие выхода из рекурсии.
Что такое исключения (Exceptions)
А теперь об Exception. Эти исключительные ситуации возникают, если разработчик допустил невыполнимую операцию, не предусмотрел особые случаи в бизнес-логике программы (или сообщает о них с помощью исключений).
1. Невыполнимая операция
Мир не рухнул, как в случае с Error, просто Java не знает, что делать дальше. Как раз из этого разряда деление на ноль в начале статьи: и правда, какое значение тогда присвоить переменной oops?
Убедитесь сами, что исключение класса ArithmeticException наследуется как раз от Exception.
Стоит запомнить. В IntelliJ IDEA, чтобы увидеть положение класса в иерархии, выберите его и нажмите Ctrl + H (или на пункт Type Hierarchy в меню Navigate).
Другая частая ситуация — обращение к несуществующему элементу массива. Например, у нас в нём десять элементов, а мы пытаемся обратиться к одиннадцатому.
2. Особый случай в бизнес-логике программы
Классика. Программируем задачу о перевозке волка, козы и капусты через реку: в лодке может быть только два пассажира, но волка с козой и козу с капустой нельзя оставлять на берегу вместе. Это и есть особый случай в бизнес-логике, который нельзя нарушать.
Или пользователь вводит дату начала некоторого периода и дату его окончания. Вторая дата не может быть раньше первой.
Или, допустим, у нас есть метод, который читает файл. Сам метод написан верно. Пользователь передал в него корректный путь. Только вот у этого работника нет права читать этот файл (его роль и права обусловлены предметной областью). Что же тогда методу возвращать? Вернуть-то нечего, ведь метод не отработал. Самое очевидное решение — выдать исключение.
В дерево исключений мы ещё углубимся, а сейчас посмотрим, что и как с ними делают.
Что делать с исключениями
Простейший вариант — ничего; возникает исключение — программа просто прекращает работать.
Чтобы убедиться в этом, выполним код:
Так и есть: до деления на ноль код выполнялся, а после — нет.
Это интересно: когда возникает исключение, программисты выдают что-то вроде «код [вы]бросил исключение» или «код кинул исключение». А глагол таков потому, что все исключения — наследники класса Throwable, что значит «бросаемый» / «который можно бросить».
Второе, что можно делать с исключениями, — это их обрабатывать.
Для этого нужно заключить кусок кода, который может вызвать исключение, в конструкцию try-catch.
Как это работает: если в блоке try возникает исключение, которое указано в блоке catch, то исполнение блока try прервётся и выполнится код из блока catch.
Например:
Разберём этот код.
Если блок try кинет исключение ArithmeticException, то управление перехватит блок catch, который выведет строку «Говорили же не делить на ноль!», а значение oops станет равным 0.
После этого программа продолжит работать как ни в чём не бывало: выполнится код после блока try-catch, который сообщит: «Метод отработал».
Проверьте сами: запустите код выше. Вызовите метод hereWillBeTrouble с любыми значениями аргументов кроме нулевого b. Если в блоке try не возникнет исключений, то его код выполнится целиком, а в блок catch мы даже не попадём.
Есть ещё и третий вариант — пробросить исключение наверх. Но об этом в следующей статье.
Как читать исключение
Вернёмся к первой картинке. Посмотрим, что нам сказала Java, когда произошло исключение:
Начинаем разбирать сверху вниз:
— это указание на поток, в котором произошло исключение. В нашей простой однопоточной программе это поток main.
— какое исключение брошено. У нас это ArithmeticException. А java.lang.ArithmeticException — полное название класса вместе с пакетом, в котором он размещается.
— весточка, которую принесло исключение. Дело в том, что одно и то же исключение нередко возникает по разным причинам. И тут мы видим стандартное пояснение «/ by zero» — из-за деления на ноль.
— это самое интересное: стектрейс.
Стектрейс (Stack trace) — это упорядоченный список методов, сквозь которые исключение пронырнуло.
У нас оно возникло в методе hereWillBeTrouble на 8-й строке в классе Main (номер строки и класс указаны в скобках синим). А этот метод, в свою очередь, вызван методом main на 3-й строке класса Main.
Стектрейсы могут быть довольно длинными — из десятков методов, которые вызывают друг друга по цепочке. И они здорово помогают расследовать неожиданно кинутое исключение.
Советую закреплять теорию на практике. Поэтому вернитесь в блок про Error и вызовите метод notGood — увидите любопытный стектрейс.
Как создать исключение
Всё это время мы имели дело с исключением, которое бросает Java-машина — при делении на ноль. Но как вызвать исключение самим?
Раз исключение — это объект класса, то программисту всего-то и нужно, что создать объект с нужным классом исключения и бросить его с помощью оператора throw.
При создании большинства исключений первым параметром в конструктор можно передать сообщение — мы как раз сделали так выше.
А получим мы то же самое, что и в самом первом примере, только вместо стандартной фразы «/by zero» теперь выдаётся наш вопрос-пояснение «ты опять делишь на ноль?»:
Что дальше?
В следующей статье мы углубимся в иерархию исключений Java, узнаем про их разделение на checked и unchecked, а также о том, что ещё интересного можно с ними делать.