Исключения

Исключения (exceptions) в Java представляют собой события или ситуации, которые возникают во время выполнения программы и могут нарушить нормальный ход её работы. Это могут быть ошибки ввода-вывода, неправильные аргументы, деление на ноль и множество других ситуаций, когда программа не может продолжить выполнение в обычном режиме. Кто не сталкивался с ужасающим текстом “a java exception has occured, caused by …” или “exception in thread main” в консоли?

В языке Java есть множество типов исключений. Но программисты могут создавать и свои собственные – чтобы контролировать результаты выполнения программы. Если метод выбросил исключение определённого типа – значит, в нём произошла ошибка. Следующие действия программы должны учитывать это.

Давайте разберёмся с темой исключений и посмотрим, как это можно применить на практике. Также, что немаловажно – зачем это вообще применять.

Виды исключений

Рассмотрим несколько видов исключений, используемых в языке Java. Они представляют собой специальные классы, которые созданы на основе класса Exception, олицетворяющего мать всех исключений.

  1. ArithmeticException – вызывается при арифметических ошибках, таких, как деление на ноль.
  2. NullPointerException – сигнализирует о попытке обратиться к объекту, который не имеет значения (имеет значение null).
  3. ArrayIndexOutOfBounds – бросается при попытке доступа к элементу массива с недопустимым индексом (-1 или 5, например, когда индекс последнего элемента равен 4).
  4. IndexOutOfBoundsException – это обобщённое исключение для выхода за пределы диапазона (как ArrayIndexOutOfBounds, так и другие схожие исключения, например, StringIndexOutOfBoundsException).
  5. IllegalArgumentException – генерируется, когда переданные аргументы в метод имеют недопустимое значение.
  6. IOException – исключение для ошибок ввода-вывода, например, при работе с файлами.

Попробуем словить парочку исключений.

Сперва обратимся к элементу по индексу 3 массива, в котором всего 3 элемента с индексами 0, 1 и 2. Третьего не дано.

Скриншот консоли IntelliJ IDEA с исключением ArrayIndexOutOfBoundsException

Исключение не заставило себя ждать. Так, и с каким же зверем мы столкнулись здесь? Это тот самый ArrayIndexOutOfBoundsException, который мы рассмотрели чуть выше в списке. Дальше написано пояснение красным цветом, что индекс 3 – многовато для массива размером 3. Соответственно, в консоль выводится также название файла и порядковый номер строки, в которой произошло исключение. Это очень удобно для отладки (MyProject.java, строка 4).

Попробуем разделить целое число на ноль.

Скриншот консоли IntelliJ IDEA с исключением ArithmeticException

Результат налицо – ArithmeticException и пояснение, что в 3-ей строке файла MyProject.java произошло нечто ужасное (деление на ноль).

Ладно бы деление на ноль вызывало исключение всегда. Но есть одна фишка.. Если разделить на ноль нецелое число, то результат будет совсем другим.

Разделили на 0 и разделили, из-за чего тут сыр-бор? Оказывается, в Java есть специальные значения для вещественных чисел, и одно из них – бесконечность (Infinity). Так что при делении на 0 вещественного числа не выдаётся никакого исключения. Просто в переменную записывается специальное значение.

Скриншот консоли IntelliJ IDEA с выведенным значением Infinity

Как отлавливать исключения

Исключения мы вызвали. Но что делать с ними? Программа прекращается и не выполняется дальше, а в консоль выводятся красные строки. Не хочется при каждой ошибке завершать программу. Для этого мы с вами научимся исключения ловить. Это осуществляется с помощью блока try-catch-(иногда вдобавок и finally).

Блок try содержит в себе код, который может спровоцировать исключение. Если исключение брошено в блоке try, осуществляется переход в блок catch. Код этого блока выполняется только в случае, если в блоке try произошла ошибка. В конце концов выполняется код блока finally. Причём он выполняется в любом случае, вне зависимости, было ли выброшено исключение. Даже если поставить return в блоке try или catch, код блока finally всё равно выполнится перед завершением работы метода. Блок finally необязателен, например, лично я его использую редко.

Напишем программу, которая предлагает ввести пользователю 2 целых числа, а затем делит их друг на друга. Если при вычислении случается какая-либо арифметическая ошибка (ArithmeticException), тогда она обрабатывается и выводится в консоль. Программа корректно продолжает работу.

Согласно соглашению о кодировании, ключевые слова catch и finally нужно располагать на тех же строках, на каких ставятся закрывающие фигурные скобки предыдущего блока.

Скриншот консоли IntelliJ IDEA с выполнением программы по делению чисел

Внимательно: в обоих случаях выполняется содержимое блока finally. Выполняется оно вне зависимости от того, было ли выброшено исключение ArithmeticException. Программа завершается корректно в обоих случаях (с кодом 0).

Скриншот консоли IntelliJ IDEA с выполнением программы по делению чисел и обработке исключения при делении на ноль

Иерархия исключений в Java

Все исключения в Java являются объектами. Они происходят от класса Throwable, который сам разделяется на два основных подкласса: Exception и Error.

Exception – мать всех классов-исключений. Это обычно несерьёзная ожидаемая программистом ошибка, которая произошла в программе. С ней можно жить, если её обработать.

Но Error – это дело посерьёзнее. Этот класс представляет большие проблемы, которые обычно вряд ли можно обработать в рамках обычной программы. Примеры – OutOfMemoryError, StackOverflowError. Обычно ловить их не рекомендуется.

В большинстве своём исключения, происходящие от Exception, обрабатывать нужно обязательно. Если не поставить блок try-catch в коде, который способен их генерировать, программа просто не скомпилируется. Но как – ведь мы и обращались к несуществующему элементу массива, и делили на 0. Всё это происходило без блока try-catch. И программа компилировалась и запускалась, а исключение приходило уже по ходу работы программы.

Это особый вид исключений. Они созданы на основе RuntimeException. RuntimeException – это подраздел исключений во время выполнения Java-программы, происходящий от Exception. Исключения, происходящие от RuntimeException, отлавливать в программе необязательно. Однако, важно знать, что их можно и нужно обрабатывать, если вы знаете, что они могут возникнуть в вашем коде. Примеры таких исключений: как раз ArrayIndexOutOfBoundsException, ArithmeticException и ещё, к примеру, NullPointerException.

Создание своих исключений

Можно использовать не только готовые исключения, но и создавать новые под свои нужды. Ведь давая имя типу ошибки вы повышаете читаемость кода. Становится понятно, какого рода ошибки могут возникнуть в той или иной части программы.

Так как исключение – это отдельный класс, создадим его в новом файле:

Показать, что наш класс NicknameException происходит от базового класса исключений Exception, можно с помощью ключевого слова extends. В конструкторе принимается один аргумент – сообщение, чтобы раскрыть суть ошибки. С помощью ключевого слова super вызывается такой же конструктор у базового класса Exception. И нам больше ни о чём думать не нужно. Подробнее о конструкциях происхождения от других классов (наследования) в статьях про ООП.

Теперь используем созданное исключение в программе. Как бросать (генерировать) исключения в Java? Собственноручно делать это можно с помощью ключевого слова throw.

Когда метод может выбрасывать исключение, но не обрабатывает его внутри себя (с помощью блока try-catch), он обязан объявить это с помощью ключевого слова throws. При вызове такого метода, вызывающему коду также нужно будет обработать это исключение или также использовать throws.

Метод checkNicknameOrThrown() позволяет проверить корректность имени игрока. И хотя он ничего не возвращает (void), он передаёт информацию с помощью исключения. Если исключения нет, значит ник верный. Если есть – то можно получить сообщение, заложенное в брошенном исключении при помощи метода ex.getMessage(). Затем можно даже вывести, в чём конкретно была ошибка. Ведь игроку недостаточно сообщить, что ник некорректен. А как ему с этой информацией исправить ник, если он не узнает, что именно не так?

В методе checkNicknameOrThrown() нет блока try-catch. Ошибка генерируется при помощи throw new NicknameException(), но не обрабатывается. Поэтому необходимо в шапку метода приписать throws NicknameException. Это будет знаком, что метод может выбросить исключение. Делается это для того, чтобы в месте, откуда его вызывают, исключение обработали при помощи блока try-catch. Именно так и происходит в программе. Вызов метода checkNicknameOrThrown() помещён в блок try-catch в методе main().

В итоге, благодаря циклу while (true) программа не перестанет спрашивать игрока о его нике до тех пор, пока он не введёт что-то вразумительное. Тогда цикл завершится по break.

Скриншот консоли IntelliJ IDEA с выполнением программы по обработке введённого ника игрока

В общем, исключения в Java – это мощный механизм для обработки ошибок и нештатных ситуаций в коде. Путём создания собственных исключений, вы можете явно указать, какие исключительные ситуации могут возникнуть в коде. Это позволит обеспечить их более точную обработку. Код становится более читаемым. Избегайте сложных схем обработки ошибок, связанных с возвращением кодов ошибок. Бросайте исключения в своё удовольствие!

Добавить комментарий