Обработка исключений в Java

Во время работы программы многое может пойти не так: может закончится память на компьютере, файл которые мы хотели бы подгрузить в программу не соответствует нужному формату или в результате ошибки программиста приложение обращается к объекту, который уже давно в памяти не существует. Для обработки подобных ситуаций в Java предусмотрен механизм исключений. В результате возникновения исключительных ситуаций пользователь может потерять все результаты своей работы, а это согласитесь не очень приятно. После прочтения этой статьи вы сможете убедиться, что обработка исключений в Java не самая сложная задача и разработчику для этого предоставляется множество инструментов. Все типы исключений в Java являются подклассами от встроенного класса Throwable.

Обработка исключений в Java

Как можно заметить иерархия наследования исключений делится на две ветки: Exception и Error.

все типы исключений в Java являются подклассами от встроенного класса Throwable

Error означает что произошло что-то по-настоящему ужасное с чем справится ваша программа уже не в состоянии. Класс Error описывает внутренние ошибки и ситуации, возникающие в связи с нехваткой ресурсов в исполняющей системе Java. Примером такой ситуации может служить переполнение памяти.

Ошибки выполнения реализованы классом RuntimeException и его подклассами. Исключения подобного типа, как правило, всегда возникают по вине программиста. Наиболее часто встречающиеся исключения подобного типа это – ArrayIndexOutBoundsException и NullPointerException. Исключения типа Error, RuntimeException и производные от них классы относятся к типу непроверяемых (необрабатываемых) исключений.

проверяемое исключение должно быть или обработано, или объявлено в описании метода

Проверяемые (обрабатываемые) исключения включают в себя класс Exception и все его подклассы за исключением класса Error, RuntimeException. Появление обрабатываемых исключений в ходе программы более ожидаемо, чем не обрабатываемых, например, попытка прочитать файл, который не существует. В Java существует правило: «обработай или объяви». Для проверяемых исключений это означает, что исключение должно быть или обработано, или объявлено в описании метода. Маленький пример:

Обратите внимание, что в данном примере ключевое слово throw означает, что мы хотим сгенерировать исключение класса Exception, а ключевое слово throws в заголовке метода сообщает, что метод может сгенерировать исключение, а может и нет. Для проверяемых исключений, как мы видим, идея очень проста – если в методе генерируется проверяемое исключения (throw new Exception();), то метод должен сам его обработать, если он обработать его не в состоянии, то эта обязанность перекладывается на метод вызвавший его. Если метод может генерировать несколько проверяемых исключений все они должны быть перечислены в заголовке метода через запятую:

Обратите внимание, несмотря на то, что метод объявил о том, что в результате его работы может быть сгенерировано два типа исключений (throws FileNotFoundException, EOFException), в самом теле метода ничего не происходит — оно попросту пустое, но это не освобождает нас от правила «обработай или объяви». Поэтому при вызове метода ex() из последнего примера мы должны использовать оператор try или в вызывающем методе объявить о генерации исключений — throws FileNotFoundException, EOFException.

Таким же образом можно объявлять в заголовке метода непроверяемые исключения, но делать это не рекомендуется.



Перехват исключений.

Перехват исключений осуществляется в блоке try/catch. Общий вид:

Первое идет ключевое слово try, далее блок кода (защищенный блок кода) в фигурных скобках (фигурные скобки обязательны). После защищенного блока кода располагается ключевое слово catch (обязательно, если не присутствует блок finally), после которого в скобках указывается тип исключения, которое необходимо обработать, в следующих фигурных скобках, располагается код, который так или иначе обрабатывает сгенерированное исключение. Если выполнение кода в блоке try происходит без каких-либо проблем, то блок catch игнорируется, если же в блоке try возникает исключение, то выполнение кода прекращается и передается блоку catch c соответствующем исключением. Пример:

Вывод:
1 2 4 5

Как видите, после того как было сгенерировано исключение в методе ex(), выполнение программы перешло к блоку catch() и код System.out.print(“ 3 “); выполнен не был. После обработки исключения выполнение программы продолжилось в методе main().

Немного о том, что можно сделать не так:

В результате компиляции этого кода возникнет ошибка, фигурные скобки для блоков try/catch обязательны!

В этом случае пропущен блок catch, что тоже приведет к ошибке компиляции.

Блок finally.

Общий вид:

Если в методе генерируется исключение оставшиеся в нем операторы не выполняются, тогда возникает следующая проблема – если в методе задействованы локальные ресурсы, то они не высвобождаются. В этом случае на помощь придет блок finally. Принцип его работы очень прост – он выполняется всегда при любых обстоятельствах. Если в блоке try не было сгенрировано никаких исключений, то следом выполняется блок finally, если в блоке try было сгенерировано исключение, то выполняется блок catch, а затем finally. Есть только одно исключение – если перед блоком finally был сделан вызов System.exit(0), то программа завершает свою работу без выполнения блока finally.

если перед блоком finally был сделан вызов System.exit(0), то программа завершает свою работу без выполнения блока finally

Обратите внимание, что последовательность блоков очень важна, нельзя использовать finally перед catch. Небольшой пример:

Вывод:
1 2 4 5 6

Как видно блок finally выполнился сразу за блоком catch, а после передал управление методу main(). Давайте рассмотрим более интересный пример:

Вывод:
1 2 4

Если Вы заметили в примере отсутствует блок catch. И так во время вызова метода ex() генерируется исключение, которое мы никак не обрабатываем, управление передается блоку finally после выполнения которого, исключение передается обратно вызывающей части программы.

Как вы думаете, что произойдет в результате выполнения этого кода:

В блоке try генерируется исключение, которое обрабатывается блоком catch, который в свою очередь тоже генерирует исключение класса RuntimeException, но как говорилось выше блок finally выполняется всегда. Блок finally генерирует новое исключение класса CustomException, которое скрывает исключение из блока catch. В результате выполнения этого кода получаем:

Exception in thread «main» example.CustomException

Существует небольшая хитрость, которой пользуются многие программисты, она заключается в разделении блоков try/cath и try/finally, что делает код более понятным:

В примере внутренний блок try отвечает только за закрытие потока ввода, а внешний блок try сообщает об ошибках. Несомненный плюс подобного подхода в том, что ошибки выявляются и в блоке finally.



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

В результате работы программы может возникнуть такая ситуация, которая не будет предусмотрена ни одним из существующих классов исключений. В подобном случае решением будет создание собственного класса исключения, думаю очевидно, что он должен быть подклассом Exception или или одного из его подклассов (например, IOException). В этот класс можно добавить конструктор по умолчанию и конструктор, который содержит сообщение об ошибке:

Обработка нескольких типов исключений.

Для того, чтобы обработать несколько разных типов исключений необходимо для каждого из них создать свой блок catch:

В примере присутствует два класса исключений: Exception и унаследованный от него CustomException. В теле программы присутствует два блока catch, которые обрабатывают эти два типа исключений, т.к. метод ex() генерирует исключение класса Exception, то сработает только последний блок catch. При подобном подходе к обработке исключений следует учитывать одно очень важное правило, которое касается очередности блоков catch — Java обрабатывает эти блоки в порядке очередности и если возникает ситуация что один из блоков catch выполнен не будет ни при каких обстоятельствах возникнет ошибка компиляции с сообщением о недостижимости кода (unreachable code). Подобное происходит, когда исключение суперкласса идет перед исключением подкласса. В нашем примере суперкласс – Exception, подкласс – CustomException, если мы поменяем блоки catch местами и будем сначала обрабатывать исключения класса Exception, то при компиляции получим сообщение о недостижимости кода:

И еще один пример с недостижимостью кода:

В этом примере метод ex() не генерирует никаких проверяемых исключений, поэтому блок catch(CustomException e){} не будет выполнен ни при каких условиях, о чем нам будет рад сообщить компилятор.

Начиная с Java SE7, разнотипные исключения можно перехватывать в одном блоке catch:

Такой способ перехвата исключений следует использовать, когда перехватываемые исключения не являются подклассами друг друга. При перехвате нескольких исключений в одном блоке переменная исключения неявно считается конечной (final), в примере выше переменной e нельзя присвоить другое значение в блоке catch.

начиная с Java SE7, разнотипные исключения можно перехватывать в одном блоке catch

Блоки try/catch/finally допускают использование вложенных операторов try:

Вывод:
1 2 3 5 6 8

Как можно заметить в примере после обработки внутренним оператором try исключения, блок catch внешнего оператора try игнорируется, и программа продолжает работать дальше.



О наследовании.

Когда класс переопределяет метод суперкласса или реализует метод интерфейса, не допускается добавлять новые проверяемые исключения в заголовке метода. К примеру, следующий код недопустим:

Подклассу допускается в переопределенном методе объявить подклассы исключений, объявленных в суперклассе или вообще не объявлять никаких исключений:

Выше описанные правила распространяются только на проверяемые исключения, непроверяемые исключения вы свободны объявлять в переопределяемых методах где захотите без оглядки на суперкласс:

Вывод сообщений об исключениях.

Существует три варианта вывести информацию о сгенерированном исключении:

  1. Вывести информацию с типом исключения и сообщением
  2. Вывести только сообщение
  3. Первый пункт с трассировкой стека. Трассировка стека – это список вызовов методов в данной точке выполнения программы.

Вывод:
java.lang.RuntimeException: Hello Exception!!!
Hello Exception!!!
java.lang.RuntimeException: Hello Exception!!!
at example.Example.ex(Example.java:18)
at example.Example.main(Example.java:9)

Как Вы могли заметить для текстового описания трассировки стека используется метод printStackTrace().

Site Footer