Обработка исключений в Java
В Java обработка исключений — важная концепция программирования, используемая для обработки ошибок или исключительных ситуаций, которые могут возникнуть во время выполнения программы.
Исключения — это некоторые ошибки в программе, но не все ошибки являются исключениями, и ошибок иногда можно избежать.
Например, если в вашем коде пропущена точка с запятой, то выводится сообщение об ошибкеjava.lang.Error, если вы используетеSystem.out.println(11/0), то, поскольку вы используете0в качестве делителя, будет выброшеноjava.lang.ArithmeticException— исключение.
Существует много причин исключений, обычно включающих следующие категории:
- Пользователь ввёл недопустимые данные.
- Файл, который нужно открыть, не существует.
- Соединение прерывается во время сетевого взаимодействия, или переполняется память JVM.
Некоторые из этих исключений вызваны ошибками пользователя, некоторые — ошибками программы, а другие — физическими ошибками.
Чтобы понять, как работает обработка исключений в Java, необходимо освоить следующие три типа исключений:
Проверяемые исключения:Наиболее характерными проверяемыми исключениями являются исключения, вызванные ошибками или проблемами пользователя. Эти исключения заставляют программистов обрабатывать их во время компиляции. Например, при попытке открыть несуществующий файл возникает исключение, и такие исключения нельзя просто игнорировать во время компиляции.
Этот тип исключений обычно обрабатывается с помощьюtry-catchблоков для перехвата и обработки исключения, или с помощьюthrows-конструкций для объявления исключений, которые метод может выбросить.
try { // 可能会抛出异常的代码 } catch (IOException e) { // 处理异常的代码 }Или:
public void readFile() throws IOException { // 可能会抛出IOException的代码 }Исключения времени выполнения:Эти исключения не требуется обрабатывать во время компиляции; они обычно вызваны ошибками в программе, такими как NullPointerException, ArrayIndexOutOfBoundsException и т. д. Этот тип исключений можно обрабатывать по желанию, но это не обязательно.
try { // 可能会抛出异常的代码 } catch (NullPointerException e) { // 处理异常的代码 }Ошибки:Ошибки — это не исключения, а проблемы, находящиеся вне контроля программиста. Ошибки обычно игнорируются в коде. Например, при переполнении стека возникает ошибка, и их также нельзя обнаружить во время компиляции.
Java предоставляет следующие ключевые слова и классы для поддержки обработки исключений:
- try: используется для обертывания блока кода, который может выбросить исключение.
- catch: используется для перехвата исключений и их обработки в блоке кода.
- finally: используется для размещения блока кода, который должен выполняться независимо от того, возникает ли исключение.
- throw: используется для ручного выбрасывания исключения.
- throws: используется для указания в объявлении метода исключений, которые метод может выбросить.
- Exceptionкласс: это родительский класс всех классов исключений и предоставляет методы для получения информации об исключении, такие какgetMessage()、printStackTrace()и т. д.
Иерархия классов Exception
Все классы исключений являются подклассами, унаследованными от класса java.lang.Exception.
Класс Exception является подклассом класса Throwable. Помимо класса Exception, у Throwable также есть подкласс Error.
Программы на Java обычно не перехватывают ошибки. Ошибки обычно возникают во время серьёзных сбоев и находятся вне области обработки программами на Java.
Error используется для обозначения ошибок, возникающих в среде выполнения.
Например, переполнение памяти JVM. Обычно программы не могут восстановиться после ошибок.
У класса Exception есть два основных подкласса: класс IOException и класс RuntimeException.
Среди встроенных классов в Java (объяснённых позже) содержится большинство распространённых проверяемых и непроверяемых исключений.
Встроенные классы исключений Java
Язык Java определяет некоторые классы исключений в стандартном пакете java.lang.
Подклассы стандартного класса исключений времени выполнения являются наиболее распространёнными классами исключений. Поскольку пакет java.lang по умолчанию загружается во все программы Java, большинство исключений, унаследованных от классов исключений времени выполнения, можно использовать напрямую.
Java также определяет некоторые другие исключения на основе различных библиотек классов. В следующей таблице перечислены непроверяемые исключения Java.
| Исключение | Описание |
|---|---|
| ArithmeticException | Генерируется, когда возникает исключительная арифметическая ситуация. Например, при делении целого числа на ноль создаётся экземпляр этого класса. |
| ArrayIndexOutOfBoundsException | Исключение, возникающее при доступе к массиву с недопустимым индексом. Если индекс отрицательный или больше либо равен размеру массива, индекс недопустим. |
| ArrayStoreException | Исключение, возникающее при попытке сохранить объект неправильного типа в массив объектов. |
| ClassCastException | Генерируется при попытке привести объект к подклассу, экземпляром которого он не является. |
| IllegalArgumentException | Генерируется, чтобы указать, что методу был передан недопустимый или неподходящий аргумент. |
| IllegalMonitorStateException | Выбрасывается, чтобы указать, что поток попытался ожидать на мониторе объекта или уведомить другие потоки, ожидающие на мониторе объекта, не владея этим монитором сам. |
| IllegalStateException | Сигнализирует о том, что метод был вызван в недопустимое или неподходящее время. Иными словами, среда Java или приложение Java не находятся в подходящем состоянии, необходимом для запрошенной операции. |
| IllegalThreadStateException | Исключение, выбрасываемое, когда поток не находится в подходящем состоянии, необходимом для запрошенной операции. |
| IndexOutOfBoundsException | Выбрасывается, чтобы указать, что некоторый индекс (например, для массива, строки или вектора) выходит за пределы допустимого диапазона. |
| NegativeArraySizeException | Выбрасывается, если приложение пытается создать массив с отрицательным размером. |
| NullPointerException | Когда приложение пытается использоватьnullтам, где требуется объект, выбрасывается это исключение. |
| NumberFormatException | Выбрасывается, когда приложение пытается преобразовать строку в числовой тип, но строка не может быть преобразована в соответствующий формат. |
| SecurityException | Исключение, выбрасываемое менеджером безопасности для указания на нарушение безопасности. |
| StringIndexOutOfBoundsException | Это исключение выбрасываетсяStringметодом, чтобы указать, что индекс либо отрицателен, либо больше размера строки. |
| UnsupportedOperationException | Выбрасывается, когда запрошенная операция не поддерживается. |
В следующей таблице перечислены классы проверяемых исключений, определённые в Java в пакете java.lang.
| Исключение | Описание |
|---|---|
| ClassNotFoundException | Выбрасывается, когда приложение пытается загрузить класс, но не может найти соответствующий класс. |
| CloneNotSupportedException | При вызовеObjectметод вcloneклассе для клонирования объекта, но класс объекта не может реализоватьCloneableинтерфейс, выбрасывается это исключение. |
| IllegalAccessException | Выбрасывается, когда доступ к классу запрещён. |
| InstantiationException | При попытке использоватьClassметод вnewInstanceкласс для создания экземпляра класса, но указанный объект класса не может быть создан, потому что он является интерфейсом или абстрактным классом, выбрасывается это исключение. |
| InterruptedException | Выбрасывается, когда поток прерывается другим потоком. |
| NoSuchFieldException | Запрошенная переменная не существует. |
| NoSuchMethodException | Запрошенный метод не существует. |
Методы исключений
Ниже перечислены основные методы класса Throwable:
| Нет. | Метод и описание |
|---|---|
| 1 |
public String getMessage() Возвращает подробную информацию о возникшем исключении. Эта информация инициализируется в конструкторе класса Throwable. |
| 2 |
public Throwable getCause() Возвращает объект Throwable, представляющий причину исключения. |
| 3 |
public String Возвращает краткое описание данного Throwable. |
| 4 |
public void printStackTrace() Выводит этот Throwable и его трассировку стека в стандартный поток ошибок. |
| 5 |
public StackTraceElement [] getStackTrace() Возвращает массив, содержащий трассировку стека. Элемент с индексом 0 представляет вершину стека, а последний элемент представляет низ стека вызовов методов. |
| 6 |
public Throwable fillInStackTrace() Заполняет трассировку стека объекта Throwable текущей трассировкой стека вызовов, добавляя её к любой предыдущей информации в трассировке стека. |
Перехват исключений
Используйте ключевые слова try и catch для перехвата исключений. Блок кода try/catch размещается там, где может возникнуть исключение.
Код внутри блока кода try/catch называется защищённым кодом. Синтаксис использования try/catch выглядит следующим образом:
try
{
// 程序代码
}catch(ExceptionName e1)
{
//Catch 块
}
Предложение catch содержит объявление типа исключения, которое нужно перехватить. Когда в блоке защищённого кода возникает исключение, проверяется блок catch, следующий за try.
Если возникшее исключение включено в блок catch, исключение передаётся в этот блок catch так же, как метод передаёт аргумент.
Пример
В следующем примере объявляется массив из двух элементов. Когда код пытается обратиться к четвёртому элементу массива, выбрасывается исключение.
Код файла ExcepTest.java:
Результат компиляции и запуска приведённого выше кода выглядит следующим образом:
Exception thrown :java.lang.ArrayIndexOutOfBoundsException: 3 Out of the block
Множественные блоки catch
Ситуация, когда за блоком try следует несколько блоков catch, называется множественным перехватом.
Синтаксис нескольких блоков catch выглядит следующим образом:
Приведённый выше фрагмент кода содержит 3 блока catch.
После оператора try можно добавить любое количество блоков catch.
Если в защищённом коде возникает исключение, оно передаётся в первый блок catch.
Если тип данных выброшенного исключения соответствует ExceptionType1, оно будет перехвачено здесь.
Если он не совпадает, оно передаётся во второй блок catch.
И так далее, пока исключение не будет перехвачено или не пройдёт через все блоки catch.
Пример
Этот пример показывает, как использовать несколько try/catch.
Multi-Catch для нескольких исключений
Начиная с Java 7, был введён более краткий синтаксис — комбинированный перехват нескольких исключений, который позволяет одному блоку catch обрабатывать несколько исключений, не имеющих отношения наследования друг с другом.
try {
// 可能抛出多个不同类型异常的代码
} catch (异常类型1 | 异常类型2 | 异常类型3 异常变量) {
// 统一处理
}
Примечание:
- ExceptionType1, ExceptionType2, и т. д. не могут иметь отношения наследования; иначе возникнет ошибка компиляции.
- Имя переменной исключения — это общая ссылочная переменная для этих исключений, поэтому внутри блока catch нельзя вызывать их специфические методы.
- Компилятор выводит, что тип этой переменной исключения — ближайший общий суперкласс этих исключений (например, Exception или IOException).
Как в следующем примере, где исключения не наследуются друг от друга и могут быть выброшены как SQLException, так и IOException:
Пример
// Могут быть выброшены как SQL, так и IO исключения
doSomething();
} catch (SQLException | IOException e) {
System.out.println("Произошло исключение SQL или IO!");
e.printStackTrace();
}
Неправильный пример (нельзя объединять исключения родительского и дочернего классов):
// 错误写法!
catch (FileNotFoundException | IOException e) {
// 会导致编译错误,因为 FileNotFoundException 是 IOException 的子类
}
FileNotFoundException является подклассом IOException, поэтому такой подход фактически недопустим и вызовет ошибку компиляции.
Ключевые слова throws/throw
В Java,throwиthrowsэти ключевые слова используются для обработки исключений.
throwКлючевое слово используется для выброса исключения в коде, тогда какthrowsключевое слово используется в объявлении метода для указания типов исключений, которые могут быть выброшены.
Ключевое слово throw
throwКлючевое слово используется для выброса исключения в текущем методе.
Обычно, когда код достигает условия, при котором он не может продолжать нормальное выполнение, можно использоватьthrowключевое слово, чтобы выбросить исключение, тем самым сообщив вызывающему коду о текущем состоянии выполнения кода.
Например, в следующем коде метод проверяет, меньше ли num 0; если да, он выбрасывает IllegalArgumentException.
Пример
if (num < 0) {
throw new IllegalArgumentException("Number must be positive");
}
}
Ключевое слово throws
throwsКлючевое слово используется в объявлении метода для указания исключений, которые метод может выбросить. Когда исключение указанного типа выбрасывается внутри метода, исключение передаётся коду, который вызывает метод, и обрабатывается в этом коде.
Например, в следующем коде, когда внутри метода readFile возникает IOException, исключение передается коду, который вызывает метод. В коде, который вызывает метод, IOException должен быть перехвачен или объявлен как обрабатываемый.
Пример
BufferedReader reader = new BufferedReader(new FileReader(filePath));
String line = reader.readLine();
while (line != null) {
System.out.println(line);
line = reader.readLine();
}
reader.close();
}
Метод может объявить, что он выбрасывает несколько исключений, разделенных запятыми.
Например, следующий метод объявляет, что он выбрасывает RemoteException и InsufficientFundsException:
Ключевое слово finally
Ключевое слово finally используется для создания блока кода, который выполняется после блока кода try.
Независимо от того, возникает ли исключение, код в блоке finally всегда будет выполнен.
В блоке finally можно выполнять операторы, такие как очистка и другие завершающие операции.
Блок finally появляется после блоков catch, и синтаксис выглядит следующим образом:
Пример
Код файла ExcepTest.java:
Результат компиляции и запуска приведенного выше примера выглядит следующим образом:
Exception thrown :java.lang.ArrayIndexOutOfBoundsException: 3 First element value: 6 The finally statement is executed
Обратите внимание на следующее:
- Блок catch не может существовать независимо от try.
- Добавлять блок finally после try/catch не обязательно.
- После блока try обязательно должен быть либо блок catch, либо блок finally.
- Между блоками try, catch и finally нельзя добавлять код.
try-with-resources
После JDK 7 Java представила новуюtry-with-resourceсинтаксическую конструкцию, которая нацелена на автоматическое управление ресурсами, обеспечивая своевременное закрытие ресурсов после использования, чтобы избежать утечек ресурсов.
try-with-resources — это механизм обработки исключений, который автоматически закрывает ресурсы, объявленные в блоке try, без необходимости явно закрывать их в блоке finally.
В операторе try-with-resources нужно только объявить ресурсы после ключевого слова try, а затем указать блок кода. Независимо от того, успешно ли выполнены операции в блоке кода, ресурсы будут автоматически закрыты после завершения выполнения блока try.
try (resource declaration) {
// 使用的资源
} catch (ExceptionType e1) {
// 异常块
}
В приведённом выше синтаксисе try используется для объявления и создания ресурсов, а catch — для обработки любых исключений, которые могут быть выброшены при закрытии ресурсов.
Примечание:Оператор try-with-resources закрывает все ресурсы, реализующие интерфейс AutoCloseable.
Пример
public class ExampleTest {
public static void main(String[] args) {
String line;
try(BufferedReader br = new BufferedReader(new FileReader("test.txt"))) {
while ((line = br.readLine()) != null) {
System.out.println("Line =>"+line);
}
} catch (IOException e) {
System.out.println("IOException in try block =>" + e.getMessage());
}
}
}
Вывод приведенного выше примера:
IOException in try block =>test.txt (No such file or directory)
В приведенном выше примере мы создаем объект BufferedReader для чтения данных из файла test.txt.
В операторе try-with-resources объект BufferedReader объявляется и создается. После завершения выполнения ресурс закрывается автоматически, независимо от того, выполняется ли оператор try нормально или выбрасывает исключение.
Если возникает исключение, можно использовать catch для обработки исключения.
Теперь давайте посмотрим, как не использоватьtry-with-resourcesа вместо этого использоватьfinallyдля закрытия ресурсов. Общий объем кода намного больше, и он более сложный и громоздкий:
Пример
class ExampleTest {
public static void main(String[] args) {
BufferedReader br = null;
String line;
try {
System.out.println("Вход в блок try");
br = new BufferedReader(new FileReader("test.txt"));
while ((line = br.readLine()) != null) {
System.out.println("Line =>"+line);
}
} catch (IOException e) {
System.out.println("IOException in try block =>" + e.getMessage());
} finally {
System.out.println("Вход в блок finally");
try {
if (br != null) {
br.close();
}
} catch (IOException e) {
System.out.println("IOException в блоке finally =>"+e.getMessage());
}
}
}
}
Вывод приведенного выше примера:
Entering try block IOException in try block =>test.txt (No such file or directory) Entering finally block
Обработка нескольких ресурсов в try-with-resources
В операторе try-with-resources можно объявить несколько ресурсов, используя точки с запятой;для разделения ресурсов:
Пример
import java.util.*;
class ExampleTest {
public static void main(String[] args) throws IOException{
try (Scanner scanner = new Scanner(new File("testRead.txt"));
PrintWriter writer = new PrintWriter(new File("testWrite.txt"))) {
while (scanner.hasNext()) {
writer.print(scanner.nextLine());
}
}
}
}
Приведенный выше пример использует объект Scanner для чтения строки из файла testRead.txt и записи ее в новый файл testWrite.txt.
При объявлении нескольких ресурсов,try-with-resourcesинструкция закрывает эти ресурсы в обратном порядке. В этом примере объект PrintWriter закрывается первым, а затем закрывается объект Scanner.
Для получения дополнительной информации см.:
- try-with-resources:Оператор try-with-resources в Java
- Управление ресурсами:Класс Cleaner в Java
Объявление пользовательских исключений
В Java можно определять пользовательские исключения. При написании собственного класса исключения учитывайте следующие моменты.
- Все исключения должны быть подклассами Throwable.
- Если вы хотите написать класс проверяемого исключения, вам нужно наследоваться от класса Exception.
- Если вы хотите написать класс исключения времени выполнения, вам нужно наследоваться от класса RuntimeException.
Вы можете определить свой собственный класс исключения следующим образом:
Класс исключения, созданный только путём наследования от класса Exception, является классом проверяемого исключения.
Следующий класс InsufficientFundsException — это пользовательский класс исключения, который наследуется от Exception.
Класс исключения, как и любой другой класс, содержит переменные и методы.
Пример
Следующий пример — это имитация банковского счёта. Он идентифицирует счёт по номеру банковской карты и поддерживает операции пополнения и снятия.
Код файла InsufficientFundsException.java:
Чтобы продемонстрировать, как использовать наш пользовательский класс исключения,
Класс CheckingAccount ниже содержит метод withdraw(), который выбрасывает InsufficientFundsException.
Код файла CheckingAccount.java:
Программа BankDemo ниже демонстрирует, как вызывать методы deposit() и withdraw() класса CheckingAccount.
Код файла BankDemo.java:
Скомпилируйте вышеуказанные три файла и запустите программу BankDemo. Результат показан ниже:
Depositing $500...
Withdrawing $100...
Withdrawing $600...
Sorry, but you are short $200.0
InsufficientFundsException
at CheckingAccount.withdraw(CheckingAccount.java:25)
at BankDemo.main(BankDemo.java:13)
Общие исключения
Java определяет два типа исключений и ошибок.
- Исключения JVM (Javaвиртуальная машина)):Исключения или ошибки, выбрасываемые JVM. Например: класс NullPointerException, класс ArrayIndexOutOfBoundsException, класс ClassCastException.
- Исключения уровня программы:Исключения, выбрасываемые программами или API-программами. Например: класс IllegalArgumentException, класс IllegalStateException.
Лучшие практики обработки исключений
- Перехватывайте исключения в подходящих местах и правильно обрабатывайте их, чтобы обеспечить стабильность и надёжность программы.
- Избегайте избыточного перехвата исключений; старайтесь точно перехватывать конкретные типы исключений.
- Используйте блоки finally для освобождения ресурсов, например закрытия файлов или соединений с базой данных, чтобы гарантировать правильное освобождение ресурсов.
- Отдавайте приоритет обработке проверяемых исключений и избегайте преобразования проверяемых исключений в непроверяемые.
Обработка исключений — важная часть написания надёжных Java-приложений. Правильная обработка исключений может повысить сопровождаемость и надёжность программы.
Другие расширения