Обработка исключений в Java
Введение
Исключение – это событие ошибки, которое может произойти во время выполнения программы и нарушить ее нормальный ход. Java предоставляет надежный и объектно-ориентированный способ обработки сценариев исключений, известный как обработка исключений Java.
Исключения в Java могут возникать из-за различных ситуаций, таких как неверные данные, введенные пользователем, аппаратный сбой, сбой сетевого подключения или сервер базы данных, который не работает. Код, который указывает, что делать в определенных сценариях исключений, называется обработкой исключений.
Генерация и перехват исключений
Java создает объект-исключение при возникновении ошибки при выполнении оператора. Объект исключения содержит много отладочной информации, такой как иерархия методов, номер строки, в которой произошло исключение, и тип исключения.
Если в методе возникает исключение, процесс создания объекта исключения и передачи его в среду выполнения называется \созданием исключения. Нормальный поток программы останавливается, и среда выполнения Java Среда (JRE) пытается найти обработчик исключения.Обработчик исключений — это блок кода, который может обрабатывать объект исключения.
- Логика поиска обработчика исключений начинается с поиска в методе, в котором произошла ошибка.
- Если подходящий обработчик не найден, он переходит к вызывающему методу.
- И так далее.
Таким образом, если стек вызовов метода — A->B->C
, а в методе C
возникает исключение, то поиск соответствующего обработчика переместится из С->Б->А
.
Если соответствующий обработчик исключений найден, объект исключения передается обработчику для его обработки. Говорят, что обработчик \перехватывает исключение. Если подходящий обработчик исключения не найден, программа завершает работу и выводит информацию об исключении на консоль.
Платформа обработки исключений Java используется только для обработки ошибок времени выполнения. Ошибки времени компиляции должны быть исправлены разработчиком, написавшим код, иначе программа не будет выполняться.
Ключевые слова обработки исключений Java
Java предоставляет специальные ключевые слова для целей обработки исключений.
- throw — мы знаем, что при возникновении ошибки создается объект исключения, а затем среда выполнения Java начинает обработку для их обработки. Иногда нам может понадобиться явно генерировать исключения в нашем коде. Например, в программе аутентификации пользователей мы должны генерировать исключения для клиентов, если пароль равен
null
. Ключевое словоthrow
используется для создания исключений в среде выполнения для их обработки. - throws — когда мы выбрасываем исключение в методе, а не обрабатываем его, мы должны использовать ключевое слово
throws
в сигнатуре метода, чтобы сообщить вызывающей программе об исключениях, которые могут быть выброшены. по методу. Вызывающий метод может обрабатывать эти исключения или передавать их своему вызывающему методу с помощью ключевого словаthrows
. Мы можем предоставить несколько исключений в предложенииthrows
, и его также можно использовать с методомmain()
. - try-catch — мы используем блок
try-catch
для обработки исключений в нашем коде.try
— это начало блока, аcatch
— конец блокаtry
для обработки исключений. У нас может быть несколько блоковcatch
с блокомtry
. Блокtry-catch
также может быть вложенным. Для блокаcatch
требуется параметр, который должен иметь типException
. - finally — блок
finally
является необязательным и может использоваться только с блокомtry-catch
. Поскольку исключение останавливает процесс выполнения, у нас могут быть открыты некоторые ресурсы, которые не будут закрыты, поэтому мы можем использовать блокfinally
. Блокfinally
всегда выполняется независимо от того, произошло ли исключение или нет.
Пример обработки исключений
package com.journaldev.exceptions;
import java.io.FileNotFoundException;
import java.io.IOException;
public class ExceptionHandling {
public static void main(String[] args) throws FileNotFoundException, IOException {
try {
testException(-5);
testException(-10);
} catch(FileNotFoundException e) {
e.printStackTrace();
} catch(IOException e) {
e.printStackTrace();
} finally {
System.out.println("Releasing resources");
}
testException(15);
}
public static void testException(int i) throws FileNotFoundException, IOException {
if (i < 0) {
FileNotFoundException myException = new FileNotFoundException("Negative Integer " + i);
throw myException;
} else if (i > 10) {
throw new IOException("Only supported for index 0 to 10");
}
}
}
- Метод
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
. - У оператора
try
должен быть либо блокcatch
, либо блокfinally
, он может иметь оба блока. - Мы не можем писать код между блоками
try-catch-finally
. - У нас может быть несколько блоков
catch
с одним операторомtry
. Блоки try-catch
могут быть вложены аналогично операторамif-else
.- У нас может быть только один блок
finally
с операторомtry-catch
.
Иерархия исключений Java
Как указывалось ранее, при возникновении исключения создается объект исключения. Исключения Java являются иерархическими, и наследование используется для категоризации различных типов исключений. Throwable
— это родительский класс иерархии исключений Java, который имеет два дочерних объекта — Error
и Exception
. Исключения
далее делятся на проверенные исключения
и исключения
среды выполнения.
- Ошибки.
Ошибки
— это исключительные ситуации, которые выходят за рамки приложения, и их невозможно предвидеть и исправить. Например, сбой оборудования, сбой виртуальной машины Java (JVM) или ошибка нехватки памяти. Вот почему у нас есть отдельная иерархияError
, и мы не должны пытаться обрабатывать эти ситуации. Некоторыми из распространенныхошибок
являютсяOutOfMemoryError
иStackOverflowError
. - Проверенные исключения: проверенные
исключения
— это исключительные сценарии, которые мы можем предвидеть в программе и попытаться исправить их. Например,FileNotFoundException
. Мы должны поймать это исключение и предоставить полезное сообщение пользователю и правильно зарегистрировать его для целей отладки.Exception
является родительским классом для всех CheckedException
. Если мы выбрасываем CheckedException
, мы должныперехватить
его в том же методе, или мы должны передать его вызывающей стороне с помощьюthrows
ключевое слово. - Исключение во время выполнения:
Исключения
во время выполнения вызваны неправильным программированием. Например, попытка получить элемент из массива. Мы должны сначала проверить длину массива, прежде чем пытаться получить элемент, иначе он может вызватьArrayIndexOutOfBoundException
во время выполнения.RuntimeException
является родительским классом для всехException
среды выполнения. Если мывыбрасываем
какие-либоException
времени выполнения в методе, не требуется указывать их в предложенииthrows
сигнатуры метода. Исключений во время выполнения можно избежать, если лучше программировать.
Некоторые полезные методы классов исключений
Java Exception
и все его подклассы не предоставляют никаких конкретных методов, и все методы определены в базовом классе — Throwable
. Классы Exception
созданы для указания различных типов сценариев Exception
, чтобы мы могли легко определить основную причину и обработать Exception
в соответствии с его типом. . Класс Throwable
реализует интерфейс Serializable
для взаимодействия.
Некоторые из полезных методов класса Throwable
:
- public String getMessage() — этот метод возвращает сообщение
String
изThrowable
, и сообщение может быть предоставлено при создании исключения через его конструктор. - public String getLocalizedMessage() — этот метод предоставляется для того, чтобы подклассы могли переопределить его, чтобы предоставить вызывающей программе сообщение, зависящее от локали. Реализация этого метода в классе
Throwable
использует методgetMessage()
для возврата сообщения об исключении. - общедоступный синхронизированный Throwable getCause() — этот метод возвращает причину исключения или
null
, если причина неизвестна. - public String toString() — этот метод возвращает информацию о
Throwable
в форматеString
, возвращаемыйString
содержит имяThrowable
класс и локализованное сообщение. - public void printStackTrace() — этот метод печатает информацию о трассировке стека в стандартный поток ошибок, этот метод перегружен, и мы можем передать
PrintStream
илиPrintWriter
в качестве аргумент для записи информации о трассировке стека в файл или поток.
Автоматическое управление ресурсами Java 7 и улучшения блока Catch
Если вы перехватываете
много исключений в одном блоке try
, вы заметите, что код блока catch
в основном состоит из избыточного кода для зарегистрируйте ошибку. В Java 7 одной из функций был улучшенный блок catch
, в котором мы можем перехватывать несколько исключений в одном блоке catch
. Вот пример блока catch
с этой функцией:
catch (IOException | SQLException ex) {
logger.error(ex);
throw new MyException(ex.getMessage());
}
Существуют некоторые ограничения, например, объект исключения является окончательным, и мы не можем изменить его внутри блока catch
. Полный анализ читайте в разделе Улучшения блока Catch в Java 7.
В большинстве случаев мы используем блок finally
просто для закрытия ресурсов. Иногда мы забываем закрыть их и получаем исключения во время выполнения, когда ресурсы исчерпаны. Эти исключения трудно отлаживать, и нам может потребоваться изучить каждое место, где мы используем этот ресурс, чтобы убедиться, что мы его закрываем. В Java 7 одним из улучшений стал try-with-resources
, где мы можем создать ресурс в самом операторе try
и использовать его внутри try-catch.
блок. Когда выполнение выходит из блока try-catch
, среда выполнения автоматически закрывает эти ресурсы. Вот пример блока try-catch
с этим улучшением:
try (MyResource mr = new MyResource()) {
System.out.println("MyResource created in try-with-resources");
} catch (Exception e) {
e.printStackTrace();
}
Пример пользовательского класса исключений
Java предоставляет нам множество классов исключений, но иногда нам может понадобиться создать собственные классы исключений. Например, чтобы уведомить вызывающую сторону об определенном типе исключения с помощью соответствующего сообщения. У нас могут быть настраиваемые поля для отслеживания, например коды ошибок. Например, предположим, что мы пишем метод для обработки только текстовых файлов, поэтому мы можем предоставить вызывающей стороне соответствующий код ошибки, когда в качестве входных данных отправляется файл какого-либо другого типа.
Сначала создайте MyException
:
package com.journaldev.exceptions;
public class MyException extends Exception {
private static final long serialVersionUID = 4664456874499611218L;
private String errorCode = "Unknown_Exception";
public MyException(String message, String errorCode) {
super(message);
this.errorCode=errorCode;
}
public String getErrorCode() {
return this.errorCode;
}
}
Затем создайте CustomExceptionExample
:
package com.journaldev.exceptions;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class CustomExceptionExample {
public static void main(String[] args) throws MyException {
try {
processFile("file.txt");
} catch (MyException e) {
processErrorCodes(e);
}
}
private static void processErrorCodes(MyException e) throws MyException {
switch (e.getErrorCode()) {
case "BAD_FILE_TYPE":
System.out.println("Bad File Type, notify user");
throw e;
case "FILE_NOT_FOUND_EXCEPTION":
System.out.println("File Not Found, notify user");
throw e;
case "FILE_CLOSE_EXCEPTION":
System.out.println("File Close failed, just log it.");
break;
default:
System.out.println("Unknown exception occured, lets log it for further debugging." + e.getMessage());
e.printStackTrace();
}
}
private static void processFile(String file) throws MyException {
InputStream fis = null;
try {
fis = new FileInputStream(file);
} catch (FileNotFoundException e) {
throw new MyException(e.getMessage(), "FILE_NOT_FOUND_EXCEPTION");
} finally {
try {
if (fis != null) fis.close();
} catch (IOException e) {
throw new MyException(e.getMessage(), "FILE_CLOSE_EXCEPTION");
}
}
}
}
У нас может быть отдельный метод для обработки разных типов кодов ошибок, которые мы получаем от разных методов. Некоторые из них потребляются, потому что мы можем не захотеть уведомлять пользователя об этом, или некоторые из них мы возвращаем, чтобы уведомить пользователя о проблеме.
Здесь я расширяю Exception
, так что всякий раз, когда создается это исключение, оно должно обрабатываться в методе или возвращаться в вызывающую программу. Если мы расширяем RuntimeException
, нет необходимости указывать его в предложении throws
.
Это было дизайнерское решение. Использование Checked Exception
имеет то преимущество, что помогает разработчикам понять, какие исключения вы можете ожидать, и принять соответствующие меры для их обработки.
Лучшие практики обработки исключений в Java
- Использовать определенные исключения — базовые классы иерархии исключений не предоставляют никакой полезной информации, поэтому в 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)
Во время отладки нам придется внимательно следить за трассировкой стека, чтобы определить фактическое местоположение исключения. Если мы изменим нашу логику реализации, чтобы проверить эти исключения раньше, как показано ниже:
private static void processFile(String file) throws MyException {
if (file == null) throw new MyException("File name can't be null", "NULL_FILE_NAME");
// ... further processing
}
Затем трассировка стека исключений будет указывать, где произошло исключение, с четким сообщением:
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)
- Поймать позже — поскольку Java требует либо обработки проверенного исключения, либо объявления его в сигнатуре метода, иногда разработчики склонны
перехватывать
исключение и регистрировать ошибку. Но такая практика вредна, потому что вызывающая программа не получает никакого уведомления об исключении. Мы должныперехватывать
исключения только тогда, когда мы можем правильно их обработать. Например, в приведенном выше методе яотбрасываю
исключения обратно в вызывающий метод для их обработки. Тот же метод может быть использован другими приложениями, которые могут захотеть обработать исключение другим способом. При реализации любой функции мы всегда должныотбрасывать
исключения обратно вызывающей стороне и позволять им решать, как с ними обращаться. - Закрытие ресурсов. Поскольку исключения останавливают обработку программы, мы должны закрыть все ресурсы в блоке finally или использовать расширение Java 7
try-with-resources
, чтобы позволить среде выполнения Java закрыть их для вас. - Регистрация исключений. Мы всегда должны регистрировать сообщения об исключениях, а при
генерировании
исключений предоставлять четкое сообщение, чтобы вызывающая сторона могла легко понять, почему возникло исключение. Мы всегда должны избегать пустого блокаcatch
, который просто использует исключение и не предоставляет каких-либо значимых сведений об исключении для отладки. - Один блок перехвата для нескольких исключений. В большинстве случаев мы регистрируем сведения об исключении и предоставляем сообщение пользователю, в этом случае мы должны использовать функцию Java 7 для обработки нескольких исключений в одном
catch
блокировать. Такой подход уменьшит размер нашего кода, и он также будет выглядеть чище. - Использование пользовательских исключений. Всегда лучше определить стратегию обработки исключений во время разработки, и вместо того, чтобы
выбрасывать
несколько исключений иперехватывать
несколько исключений, мы можем создать пользовательский исключение с кодом ошибки, и вызывающая программа может обрабатывать эти коды ошибок. Также рекомендуется создать служебный метод для обработки различных кодов ошибок и их использования. - Соглашения об именах и упаковках. При создании пользовательского исключения убедитесь, что оно заканчивается на
Exception
, чтобы из самого имени было ясно, что это класс исключения. Кроме того, не забудьте упаковать их, как это сделано в Java Development Kit (JDK). Например,IOException
является базовым исключением для всех операций ввода-вывода. - Разумное использование исключений. Исключения обходятся дорого, а иногда вообще не требуется генерировать исключения, и мы можем вернуть вызывающей программе логическую переменную, чтобы указать, была ли операция успешной или нет. Это полезно, когда операция необязательна, и вы не хотите, чтобы ваша программа зависала из-за сбоя. Например, при обновлении котировок акций в базе данных из стороннего веб-сервиса мы можем захотеть избежать создания исключений в случае сбоя подключения.
- Документируйте сгенерированные исключения — используйте Javadoc
@throws
, чтобы четко указать исключения, сгенерированные методом. Это очень полезно, когда вы предоставляете интерфейс для использования другими приложениями.
Заключение
В этой статье вы узнали об обработке исключений в Java. Вы узнали о throw
и throws
. Вы также узнали о блоках try
(и try-with-resources
), catch
и finally
.