Java 8 исключения

Цикл статей «Учебник Java 8».

Следующая статья — «Java 8 потоки ввода/вывода».
Предыдущая статья — «Java 8 обобщения».

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

Исключение (exception)— это событие, которое возникает во время выполнения программы и прерывает нормальный поток выполнения инструкций.

Когда возникает какая-нибудь ошибка внутри метода, метод создаёт специальный объект, называемый объектом-исключением или просто исключением (exception object), который передаётся системе выполнения. Этот объект содержит информацию об ошибке, включая тип ошибки и состояние программы, в котором произошла ошибка. Создание объекта-исключения и передача его системе выполнения называется броском исключения (throwing an exception).

После бросания исключения система пытается найти его обработчик. Система выполнения проходит по стеку вызовов от текущего метода вверх, ища подходящий обработчик исключений.

Выбранный обработчик исключения ловит это исключение.

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

В Java все классы-исключения являются наследниками от класса java.lang.Throwable, который в свою очередь имеет подклассы java.lang.Error  и java.lang.Exception. Класс java.lang.Exception  имеет дочерний класс java.lang.RuntimeException.

java.lang.Throwable java.lang.Exception java.lang.Error java.lang.RuntimeException

Согласно соглашению все бросаемые исключения являются наследниками трёх классов: java.lang.Error , java.lang.Exception  или java.lang.RuntimeException. Технически можно бросить исключение, которое не является наследником этих трёх классов, но является наследником java.lang.Throwable, но так делать не принято.

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

Из описанных выше трёх классов выходит три вида исключений в Java:

  • Наследники java.lang.Error. Эти исключения возникают при серьёзных ошибках, после которых невозможно нормальное продолжение выполнения программы. Это могут быть различные сбои аппаратуры и т. д. В обычных ситуациях ваш код не должен перехватывать и обрабатывать этот вид исключений.
  • Наследники java.lang.RuntimeException. Это непроверяемый тип исключений вроде выхода за границу массива или строки, попытка обращения к методу на переменной, которая содержит null, неправильное использование API и т. д. В большинстве своём программа не может ожидать подобные ошибки и не может восстановиться после них. Подобные исключения возникают из-за ошибок программиста. Приложения может их перехватывать, но в большинстве случаев имеет гораздо больше смысла исправить ошибку, приводящую к подобным исключениям.
  • Наследники java.lang.Exception, которые НЕ являются наследниками java.lang.RuntimeException. Подобный тип исключений называется проверяемыми исключениями (checked exceptions). Это такой тип исключений, который может ожидать хорошо написанная программа, и из которых она может восстановить свой обычный ход выполнения. Это может быть попытка открыть файл, к которому нет доступа, или которого не существует, проблемы с доступом по сети и т. д. Все исключения являются проверяемыми, кроме наследников java.lang.Error  и java.lang.RuntimeException. Любой метод, который может бросить проверяемое исключение, должен указать это исключение в клаузе throws. Для любого кода, который может бросить проверяемое исключение, это исключение должно быть указано в throws  метода, либо должно быть перехвачено с помощью инструкции try-catch.

Перехватывание и обработка исключений

Рассмотрите следующий код:

На текущий момент этот код не будет компилироваться, так как конструктор FileOutputStream(String name)  может бросать проверяемое исключение FileNotFoundException, вызов метода os.write(byte[] b)  может бросать проверяемое исключение IOException, и вызов метода os.close()  тоже может бросать проверяемое исключение.

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

public void write(byte[] b) throws IOException

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

Чтобы пример выше компилировался, нужно весь код, который может бросить исключения поместить в блок try:

Теперь в случае возникновения исключения FileNotFoundException  управление будет передаваться на блок:

Этот блок выведет в консоль строку “Cannot find the file”, после чего управление передастся на следующую инструкцию за блоком try-catch.

Если же возникнет исключение IOException  либо один из его потомков ( FileNotFoundException  тоже является потомком IOException, но поскольку мы указали его блок первым, то в случае FileNotFoundException  будет выполняться его, специфичный блок), то будет выполняться блок, который выведет в консоль строку “Error writing file: …”, после чего управление передастся на следующую инструкцию за блоком try-catch.

Мы также могли для каждой инструкции, которая может бросить исключение, сделать свой, отдельный блок try-catch.

При возникновении исключения смотрятся блоки catch  ближайшего блока try  в том порядке, в котором они объявлены. Среда исполнения пытается сопоставить тип объекта-исключение с типом объекта-исключения, указанного в каждом из блоков catch, и выполняется первый блок catch, тип обрабатываемого исключения которого совпадает с типом брошенного исключения. Если подходящего блока catch  не нашлось, то смотрится вышестоящий блок try  и т. д.

Как уже было показано выше, обработчик исключения может обращаться к методам объекта исключения, который передаётся ему в качестве параметра.

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

Если блок catch  перехватывает несколько типов исключений, то тогда его параметр неявно final, то есть в примере выше вы не можете присвоить параметру ex  что-либо в блоке catch.

Посмотрим ещё раз на фрагмент кода из примера:

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

Чтобы избежать подобных проблем освобождение ресурсов нужно осуществлять в блоке finally. Код в блоке finally  выполняется ВСЕГДА после завершения блока try, даже в случае возникновения исключения.

Исправленный код:

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

Если же во время открытия файла на запись произойдёт ошибка, то вывод может стать таким:

Как видите блок finally  отработал после блока обработки исключений.

Если виртуальная машина Java завершит своё выполнение во время выполнения кода try  или catch, то блок finally  может НЕ выполниться. Так же если поток будет прерван внутри кода try  или catch, то блок finally  может НЕ выполниться, хотя программа продолжит своё выполнение.

Приведённый выше код можно сделать более понятным, если использовать оператор try-with-resources. Любой объект, который реализует интерфейс java.lang.AutoCloseable, который включает все объекты, который реализуют java.io.Closeable ( Closeable  расширяет AutoCloseable), например FileOutputStream , можно использовать в try-with-resources:

В этом примере try-with-resources  автоматически вызовет метод close() , что освободит ресурсы.

В блоке try-with-resources  можно указывать несколько ресурсов, тогда они будут открываться слева направо, как указано в блоке, а закрываться справа налево (то есть в обратном порядке):

Блок try-with-resources  может не иметь ни секции catch , ни finally , но обычный блок try  должен обязательно иметь либо секцию catch, либо секцию finally, либо обе секции.

Ресурсы блока try-with-resources  закрываются перед выполнением блоков catch  и finally.

Рассмотрите следующий код:

Если метод readLine()  бросит исключение, а затем метод close()  тоже бросит исключение, то метод readFirstLineFromFileWithFinallyBlock()  бросит исключение из блока finally , а исключение из блока try  будет подавлено.

Если же исключение возникнет в блоке try  и в блоке освобождающем ресурсы для try-with-resources, то конечное исключение, бросаемое методом будет исключение из блока try, то есть исходное. Это ещё одно преимущество использования try-with-resources. Исключения, которые были подавлены в блоке try-with-resources  можно получить с помощью метода public final Throwable[] getSuppressed().

Метод close()  в интерфейсе java.lang.AutoCloseable  объявляет в клаузе throws  исключение Exception, а метод close()  в  java.io.Closeable  объявляет IOException, что позволяет наследникам AutoCloseable  определять свои, специфичные для своей области исключения.

Указание типов исключений, бросаемых методом

Если какой-нибудь код внутри метода может бросать проверяемые исключения, то эти исключения должны либо перехватываться и обрабатываться внутри метода, либо метод должен указывать, что он может бросить исключение подобного вида с помощью ключевого слова throws:

Исключение IndexOutOfBoundsException  является наследником RuntimeException, поэтому указывать его не обязательно и даже не нужно:

Как бросить исключение

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

Пример:

Цепочки исключений

Приложения часто отвечает на исключение бросанием другого исключения. Цепочки исключений позволяют узнать, какое исключение привело к появлению другого исключения.

Следующие методы и конструкторы класса java.lang.Throwable  помогают работать с цепочками исключений:

Аргумент Throwable  метода initCause  и Throwable  в конструкторах — это исключения, которые привели к текущему исключению. Метод getCause()  возвращает исключение, которое стало причиной текущего исключения, а метод initCause  устанавливает причину текущего исключения:

Пример использования цепочки исключений:

В этом примере при обработке исключения IOException  создаётся новое исключение SampleException, а причина этого исключения присоединяется к цепочке исключений, и цепочка исключений бросается в следующий уровень обработчиков исключений.

Если какой-нибудь код с верхнего уровня обработчиков исключений захочет вывести стек вызовов, то ему нужно будет использовать метод getStackTrace():

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

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

Вам следует написать свои собственные классы исключений, если вы ответите «Да» на любой из следующих вопросов, в противном случае вам, вероятно, следует использовать какое-нибудь из существующих исключений:

  • Вам нужно исключение типа, который не предоставлен платформой Java?
  • Поможет ли это пользователям, если они смогут отличать ваши исключения от исключений, брошенных другими производителями?
  • Бросает ли ваш код более одного связанного исключения?
  • Если вы используете чьи-то другие исключения, то смогут ли пользователи получить доступ к этим исключениям? Или должен ли быть пакет независимым и самодостаточным?

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

ownexceptions

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

Согласно соглашению о кодировании в Java имена исключений должны заканчиваться на Exception.

Пример возможного кода для исключения GameLogicException:

Преимущества исключений

  1. Разделение кода обработки ошибок от обычного кода.
  2. Распространение информации о произошедшей ошибке вверх по стеку вызовов.
  3. Группировка и разделение различных типов ошибок.

Цикл статей «Учебник Java 8».

Следующая статья — «Java 8 потоки ввода/вывода».
Предыдущая статья — «Java 8 обобщения».

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *