Введение
Исключение – это событие ошибки, которое может возникнуть во время выполнения программы и нарушить ее нормальный ход. Java предоставляет надежный и объектно-ориентированный способ обработки исключительных ситуаций, известный как Обработка Исключений в Java.
Исключения в Java могут возникать из различных ситуаций, таких как неправильные данные, введенные пользователем, сбой оборудования, отказ в сетевом соединении или недоступность сервера базы данных. Код, который определяет, что делать в конкретных сценариях исключений, называется обработкой исключений.
Генерация и Перехват Исключений
Java создает объект исключения, когда происходит ошибка во время выполнения оператора. Объект исключения содержит много отладочной информации, такой как иерархия методов, номер строки, где произошло исключение, и тип исключения.
Если в методе происходит исключение, процесс создания объекта и передачи его среде выполнения называется «генерацией исключения». Обычный ход программы прерывается, и среда выполнения Java (JRE) пытается найти обработчик исключения. Обработчик исключения – это блок кода, который может обработать объект исключения.
- Логика поиска обработчика исключения начинается с поиска в методе, где произошла ошибка.
- Если подходящий обработчик не найден, то поиск будет продолжен в вызывающем методе.
- И так далее.
Таким образом, если стек вызовов метода выглядит как A->B->C
, и исключение возникает в методе C
, то поиск подходящего обработчика будет двигаться от C->B->A
.
Если подходящий обработчик исключения найден, объект исключения передается обработчику для обработки. Обработчик считается «перехватывающим исключение». Если подходящего обработчика исключения нет, программа завершается, и информация об исключении выводится в консоль.
Фреймворк обработки исключений Java используется только для обработки ошибок времени выполнения. Ошибки времени компиляции должны быть исправлены разработчиком при написании кода, иначе программа не будет выполнена.
Ключевые слова обработки исключений Java
Java предоставляет конкретные ключевые слова для обработки исключений.
- throw – Мы знаем, что при возникновении ошибки создается объект исключения, а затем Java runtime начинает его обработку. Иногда мы можем явным образом генерировать исключения в нашем коде. Например, в программе аутентификации пользователя мы должны генерировать исключения для клиентов, если пароль равен
null
. Ключевое словоthrow
используется для генерации исключений, которые будут обработаны runtime. - throws – Когда мы генерируем исключение в методе и не обрабатываем его, мы должны использовать ключевое слово
throws
в сигнатуре метода, чтобы предупредить программу вызывающего о возможных исключениях, которые могут быть сгенерированы методом. Метод вызывающего может обработать эти исключения или передать их своему вызывающему методу, используя ключевое словоthrows
. Мы можем указать несколько исключений в блокеthrows
, и это также может использоваться с методомmain()
. - try-catch – Мы используем блок
try-catch
для обработки исключений в нашем коде.try
– начало блока, аcatch
– в конце блокаtry
для обработки исключений. Мы можем иметь несколько блоковcatch
с блокомtry
. Блокtry-catch
может быть также вложенным. Блокcatch
требует параметра, который должен быть типаException
. - наконец – блок
finally
является необязательным и может использоваться только с блокомtry-catch
. Поскольку исключение прерывает процесс выполнения, у нас может быть открыто несколько ресурсов, которые не будут закрыты, поэтому мы можем использовать блокfinally
. Блокfinally
всегда выполняется, независимо от того, возникло исключение или нет.
Пример обработки исключений
- Метод
testException()
генерирует исключения с использованием ключевого словаthrow
. Сигнатура метода использует ключевое словоthrows
, чтобы уведомить вызывающего о типах исключений, которые он может сгенерировать. - В методе
main()
я обрабатываю исключения с использованием блокаtry-catch
в методеmain()
. Когда я не обрабатываю его, я передаю его на выполнение с помощью клавишиthrows
в методеmain()
. - Метод
testException(-10)
никогда не выполняется из-за исключения, а затем выполняется блокfinally
.
printStackTrace()
– один из полезных методов класса Exception
в целях отладки.
Этот код выведет следующее:
Outputjava.io.FileNotFoundException: Negative Integer -5
at com.journaldev.exceptions.ExceptionHandling.testException(ExceptionHandling.java:24)
at com.journaldev.exceptions.ExceptionHandling.main(ExceptionHandling.java:10)
Releasing resources
Exception in thread "main" java.io.IOException: Only supported for index 0 to 10
at com.journaldev.exceptions.ExceptionHandling.testException(ExceptionHandling.java:27)
at com.journaldev.exceptions.ExceptionHandling.main(ExceptionHandling.java:19)
Несколько важных моментов:
- Нельзя иметь блок
catch
илиfinally
без оператораtry
. - A
try
statement should have eithercatch
block orfinally
block, it can have both blocks. - Мы не можем писать код между блоками
try-catch-finally
. - Мы можем иметь несколько блоков
catch
с одним операторомtry
. - Блоки
try-catch
могут быть вложены, аналогично операторамif-else
. - Мы можем иметь только один блок
finally
с операторомtry-catch
.
Иерархия исключений Java
Как указано ранее, при возникновении исключения создается объект исключения. Исключения Java иерархические, и для категоризации различных типов исключений используется наследование. Throwable
является родительским классом иерархии исключений Java, и у него есть два дочерних объекта – Error
и Exception
. Exception
дополнительно разделяются на Проверяемые Exception
и Исключения Времени Выполнения.
- Ошибки:
Error
– исключительные ситуации, выходящие за рамки приложения, и их невозможно предвидеть и восстановить. Например, сбой оборудования, сбой виртуальной машины Java (JVM) или ошибка “недостаток памяти”. Поэтому у нас есть отдельная иерархияError
, и мы не должны пытаться обрабатывать эти ситуации. Некоторые из распространенныхError
– этоOutOfMemoryError
иStackOverflowError
. - Проверяемые исключения: Проверяемые исключения – это исключительные ситуации, которые мы можем предвидеть в программе и попытаться восстановиться от них. Например,
FileNotFoundException
. Мы должны перехватывать это исключение и предоставлять полезное сообщение пользователю, а также правильно регистрировать его для целей отладки.Exception
является родительским классом всех проверяемых исключений. Если мы бросаем проверяемое исключение, мы должны его перехватить в том же методе или передать вызывающему с использованием ключевого словаthrows
. - Исключение времени выполнения: Исключения времени выполнения вызваны плохим программированием. Например, попытка извлечения элемента из массива. Мы должны сначала проверить длину массива, прежде чем пытаться извлечь элемент, в противном случае это может вызвать
ArrayIndexOutOfBoundException
во время выполнения.RuntimeException
является родительским классом всех исключений времени выполнения. Если мы выбрасываем исключение времени выполнения в методе, не обязательно указывать их в сигнатуре метода с помощью ключевого словаthrows
. Исключения времени выполнения могут быть предотвращены с помощью лучшего программирования.
Некоторые полезные методы классов исключений
Java Exception
и все его подклассы не предоставляют конкретных методов, и все методы определены в базовом классе – Throwable
. Классы Exception
созданы для указания различных видов сценариев Exception
, чтобы мы могли легко идентифицировать корневую причину и обрабатывать Exception
в соответствии с его типом. Класс Throwable
реализует интерфейс Serializable
для взаимодействия.
Некоторые полезные методы класса Throwable
включают:
- public String getMessage() – Этот метод возвращает строку сообщения
Throwable
, и сообщение можно указать при создании исключения через его конструктор. - public String getLocalizedMessage() – Этот метод предоставляется для того, чтобы подклассы могли переопределить его и предоставить локализованное сообщение вызывающей программе. Реализация этого метода в классе
Throwable
использует методgetMessage()
для возврата сообщения об исключении. - public synchronized Throwable getCause() – Этот метод возвращает причину исключения или
null
, если причина неизвестна. - public String toString() – Этот метод возвращает информацию об
Throwable
в форматеString
, возвращаемая строка содержит имя классаThrowable
и локализованное сообщение. - public void printStackTrace() – Этот метод выводит информацию о стеке вызовов в стандартный поток ошибок, этот метод перегружен, и мы можем передать
PrintStream
илиPrintWriter
в качестве аргумента для записи информации о стеке вызовов в файл или поток.
Управление ресурсами и улучшения в блоке catch в Java 7
Если вы обрабатываете множество исключений в одном блоке try
, вы заметите, что код блока catch
в основном состоит из избыточного кода для регистрации ошибки. В Java 7 одной из особенностей был улучшенный блок catch
, где мы можем перехватывать несколько исключений в одном блоке catch
. Вот пример блока catch
с этой особенностью:
Есть некоторые ограничения, такие как то, что объект исключения является финальным, и мы не можем изменять его внутри блока catch
, подробный анализ можно прочитать в Улучшения в блоке catch в Java 7.
Большую часть времени мы используем блок finally
только для закрытия ресурсов. Иногда мы забываем их закрыть и получаем исключения времени выполнения, когда ресурсы исчерпываются. Эти исключения трудно отлаживать, и нам может потребоваться просмотреть каждое место, где мы используем этот ресурс, чтобы убедиться, что мы его закрываем. В Java 7 одним из улучшений было использование try-with-resources
, где мы можем создать ресурс в самом операторе try
и использовать его внутри блока try-catch
. Когда выполнение выходит из блока try-catch
, среда выполнения автоматически закрывает эти ресурсы. Вот пример блока try-catch
с этим улучшением:
A Custom Exception Class Example
Java предоставляет множество классов исключений для нас, но иногда нам может потребоваться создать собственные пользовательские классы исключений. Например, чтобы уведомить вызывающего о конкретном типе исключения с соответствующим сообщением. Мы можем иметь пользовательские поля для отслеживания, такие как коды ошибок. Допустим, мы напишем метод для обработки только текстовых файлов, поэтому мы можем предоставить вызывающему соответствующий код ошибки, когда вводится какой-то другой тип файла.
Сначала создайте MyException
:
Затем создайте CustomExceptionExample
:
Мы можем иметь отдельный метод для обработки различных типов кодов ошибок, которые мы получаем из различных методов. Некоторые из них потребляются, потому что мы можем не хотеть уведомлять пользователя об этом, а некоторые из них мы вернем, чтобы уведомить пользователя о проблеме.
Вот я расширяю Exception
, чтобы всякий раз, когда возникает это исключение, его нужно было обработать в методе или вернуть вызывающей программе. Если мы расширяем RuntimeException
, нет необходимости указывать это в блоке throws
.
Это было решение дизайна. Использование проверяемых Exception
имеет преимущество в том, что они помогают разработчикам понять, какие исключения можно ожидать, и предпринимать соответствующие действия по их обработке.
Лучшие практики обработки исключений в Java
- Используйте конкретные исключения – Базовые классы иерархии Exception не предоставляют полезной информации, поэтому в Java существует много классов исключений, таких как
IOException
с дополнительными подклассами, такими какFileNotFoundException
,EOFException
и т. д. Мы всегда должныthrow
иcatch
конкретные классы исключений, чтобы вызывающий знал причину исключения легко и обрабатывал их. Это облегчает отладку и помогает клиентским приложениям обрабатывать исключения должным образом. - Бросайте рано или завершайте быстро – Мы должны стараться бросать исключения как можно раньше. Рассмотрим вышеуказанный метод
processFile()
: если мы передадим в этот метод аргументnull
, мы получим следующее исключение:
OutputException in thread "main" java.lang.NullPointerException
at java.io.FileInputStream.<init>(FileInputStream.java:134)
at java.io.FileInputStream.<init>(FileInputStream.java:97)
at com.journaldev.exceptions.CustomExceptionExample.processFile(CustomExceptionExample.java:42)
at com.journaldev.exceptions.CustomExceptionExample.main(CustomExceptionExample.java:12)
Во время отладки мы должны внимательно рассматривать стек вызовов, чтобы выявить фактическое место возникновения исключения. Если мы изменяем логику реализации, чтобы проверять эти исключения заранее, как показано ниже:
, то стек вызовов исключения будет указывать, где произошло исключение с четким сообщением:
Outputcom.journaldev.exceptions.MyException: File name can't be null
at com.journaldev.exceptions.CustomExceptionExample.processFile(CustomExceptionExample.java:37)
at com.journaldev.exceptions.CustomExceptionExample.main(CustomExceptionExample.java:12)
- Catch Late – Поскольку Java требует либо обработать проверяемое исключение, либо объявить его в сигнатуре метода, иногда разработчики склонны перехватывать исключение и записывать ошибку в лог. Но эта практика вредна, потому что вызывающая программа не получает уведомления об исключении. Мы должны перехватывать исключения только тогда, когда можем обработать их должным образом. Например, в указанном выше методе я использую оператор
throw
, чтобы вернуть исключения вызывающему методу для их обработки. Тот же самый метод может использоваться другими приложениями, которые могут захотеть обработать исключение по-другому. При реализации любой функции мы всегда должны использовать операторthrow
, чтобы вернуть исключения вызывающему и позволить им решить, как с ними обращаться. - Закрытие ресурсов – Поскольку исключения приостанавливают выполнение программы, мы должны закрывать все ресурсы в блоке finally или использовать улучшение Java 7
try-with-resources
, чтобы позволить Java автоматически закрыть их. - – Регистрация исключений – Мы всегда должны регистрировать сообщения об исключениях и при выбрасывании исключений предоставлять четкое сообщение, чтобы вызывающая сторона легко понимала, почему произошло исключение. Мы всегда должны избегать пустого блока
catch
, который просто обрабатывает исключение, не предоставляя каких-либо значимых подробностей об исключении для отладки. - Одиночный блок
catch
для нескольких исключений – В большинстве случаев мы регистрируем подробности об исключениях и предоставляем сообщение пользователю, в этом случае мы должны использовать функцию Java 7 для обработки нескольких исключений в одном блокеcatch
. Этот подход уменьшит размер нашего кода и будет выглядеть более чисто. - Использование пользовательских исключений – Всегда лучше определить стратегию обработки исключений на этапе проектирования, а вместо выбрасывания и обработки нескольких исключений мы можем создать пользовательское исключение с кодом ошибки, и вызывающая программа может обрабатывать эти коды ошибок. Также хорошей идеей является создание утилитарного метода для обработки различных кодов ошибок и их использования.
- Соглашения об именах и упаковка – При создании пользовательского исключения убедитесь, что оно заканчивается на
Exception
, чтобы уже из самого названия было понятно, что это класс исключения. Также убедитесь, что они упакованы, как это делается в Java Development Kit (JDK). Например,IOException
является базовым исключением для всех операций ввода-вывода. - Используйте Исключения Осмотрительно – Исключения затратны, и иногда их вовсе не нужно выбрасывать, и мы можем вернуть логическую переменную в вызывающую программу, чтобы указать, успешно ли выполнена операция или нет. Это полезно там, где операция необязательна, и вы не хотите, чтобы ваша программа застревала из-за ошибки. Например, при обновлении котировок на бирже в базе данных из веб-сервиса третьей стороны мы можем избежать выбрасывания исключений, если соединение не установлено.
- Документируйте Брошенные Исключения – Используйте Javadoc
@throws
, чтобы четко указать исключения, выбрасываемые методом. Это очень полезно, когда вы предоставляете интерфейс для использования другими приложениями.
Вывод
В этой статье вы узнали о обработке исключений в Java. Вы узнали о throw
и throws
. Вы также узнали о блоках try
(и try-with-resources
), catch
и finally
.
Source:
https://www.digitalocean.com/community/tutorials/exception-handling-in-java