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

Обработка исключений (exception handling) в Java позволяет разработчикам эффективно управлять ошибками во время выполнения программы с помощью таких механизмов, как блоки try-catch, finally, генерация исключений (throwing Exceptions), обработка пользовательских исключений (Custom Exception handling) и др. Исключение (Exception) — это нежелательное или неожиданное событие, которое происходит во время выполнения программы, то есть в runtime, и нарушает нормальный ход её работы. Исключение возникает, когда происходит что-то непредвиденное, например, попытка обратиться к недопустимому индексу, деление на ноль или открытие несуществующего файла.

В Java исключение — это ошибка, возникающая при сбое выполнения программы.

Пример: демонстрация арифметического исключения, или иначе — деление на ноль.

import java.io.*;

class Geeks {
    public static void main(String[] args) {
        int n = 10;
        int m = 0;

        int ans = n / m;

        System.out.println("Answer: " + ans);
    }
}

Вывод:

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

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

Обработка исключений в Java — это эффективный механизм для управления ошибками во время выполнения, который обеспечивает сохранение нормального рабочего процесса приложения. К распространённым исключениям относятся ClassNotFoundException, IOException, SQLException, RemoteException и другие. Благодаря обработке исключений Java позволяет создавать устойчивые и отказоустойчивые приложения.

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

// Java программа, демонстрирующая обработку
// исключения с помощью try-catch блока
import java.io.*;

class Geeks {
    public static void main(String[] args) {
        int n = 10;
        int m = 0;

        try {
            // Код, который может привести к исключению
            int ans = n / m;
            System.out.println("Answer: " + ans);
        } catch (ArithmeticException e) {
            // Обработка исключения
            System.out.println("Error: Division by zero is not allowed!");
        } finally {
            System.out.println("Program continues after handling the exception.");
        }
    }
}

Вывод:

Error: Division by zero is not allowed!
Program continues after handling the exception.

Обратите внимание:

  • С помощью обработки исключений можно обнаруживать и корректно обрабатывать ошибки, что позволяет сохранить нормальный ход выполнения программы.
  • Блок finally выполняется всегда, независимо от того, возникло исключение или нет, что делает его надёжным местом для операций очистки.

Иерархия исключений в Java

Все типы исключений и ошибок являются подклассами класса Throwable, который является корнем иерархии. Одна ветвь идет от класса Exception — это исключения, которые пользовательский код должен обрабатывать. Пример — NullPointerException. Другая ветвь — Error, которая используется системой Java (JVM) для обозначения ошибок, связанных с самой средой выполнения (JRE), например StackOverflowError.

Ниже представлена схема иерархии исключений в Java:

Основные причины возникновения исключений

Исключения могут возникать по разным причинам, таким как:

  • Неверный ввод пользователя
  • Сбой оборудования
  • Потеря сетевого соединения
  • Физические ограничения (недостаток места на диске)
  • Ошибки в коде
  • Выход за границы массива
  • Обращение к null-ссылке
  • Несоответствие типов
  • Открытие отсутствующего файла
  • Ошибки базы данных
  • Арифметические ошибки

Ошибки (Errors) — это неисправимые состояния, например, нехватка памяти JVM, утечки памяти, переполнение стека, несовместимость библиотек, бесконечная рекурсия и прочее. Ошибки обычно выходят за пределы контроля программиста и обрабатывать их не следует.

Различия между Error и Exception

Error Exception
Ошибка указывает на серьёзную проблему, которую разумное приложение не должно пытаться поймать. Исключение указывает на условия, которые разумное приложение может попытаться обработать.
Ошибка возникает из-за проблем с JVM или оборудованием. Исключение вызывается условиями в программе, например, неверным вводом или ошибками логики.
Примеры: OutOfMemoryError, StackOverflowError Примеры: IOException, NullPointerException

Для подробного изучения различий смотрите статью: Exception vs Errors in Java.

Типы исключений в Java

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

Исключения можно классифицировать на два типа:

  1. Встроенные исключения (Built-in Exceptions)
    • Checked Exception (проверяемые исключения)
    • Unchecked Exception (непроверяемые исключения)
  2. Пользовательские исключения (User-Defined Exceptions)

1. Встроенные исключения

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

1.1 Проверяемые исключения (Checked Exceptions)

Проверяемые исключения — это так называемые исключения времени компиляции (compile-time exceptions), так как компилятор проверяет их наличие. Примеры проверяемых исключений:

  • ClassNotFoundException: возникает, когда программа пытается загрузить класс во время выполнения, но класс не найден, так как отсутствует или неправильно расположен в проекте.
  • InterruptedException: выбрасывается, когда поток приостанавливается, а другой поток прерывает его.
  • IOException: возникает при сбое операций ввода/вывода.
  • InstantiationException: выбрасывается при попытке создать объект класса, но это невозможно, потому что класс абстрактный, интерфейс или у него нет конструктора по умолчанию.
  • SQLException: возникает при ошибках работы с базой данных.
  • FileNotFoundException: возникает при попытке открыть несуществующий файл.

1.2 Непроверяемые исключения (Unchecked Exceptions)

Непроверяемые исключения — противоположность проверяемым: компилятор не проверяет их наличие при компиляции. Иначе говоря, если программа выбрасывает непроверяемое исключение и не обрабатывает его, ошибка компиляции не возникает. Примеры непроверяемых исключений:

  • ArithmeticException: возникает при недопустимой математической операции.
  • ClassCastException: возникает при попытке привести объект к классу, к которому он не принадлежит.
  • NullPointerException: возникает при обращении к null-объекту, например, попытке вызвать метод или получить поле.
  • ArrayIndexOutOfBoundsException: возникает при обращении к элементу массива с недопустимым индексом.
  • ArrayStoreException: возникает при попытке сохранить объект неправильного типа в массиве.
  • IllegalThreadStateException: выбрасывается, когда операция с потоком не допускается в текущем состоянии потока.

Обратите внимание: Подробнее о проверяемых и непроверяемых исключениях в статье: Checked vs Unchecked Exceptions.

2. Пользовательские исключения (User-Defined Exception)

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

Методы вывода информации об исключении

Метод Описание
printStackTrace() Выводит полный стек вызовов исключения — имя, сообщение и место ошибки.
toString() Выводит информацию об исключении в формате названия исключения.
getMessage() Выводит описание исключения.

Блок try-catch

Блок try-catch в Java служит для обработки исключений. В блоке try размещается код, который может привести к исключению, а в блоке catch — код обработки этих исключений, если они возникнут.

try {
    // Код, который может привести к исключению
} catch (ExceptionType e) {
    // Код обработки исключения
}

Блок finally

Блок finally выполняется в любом случае — независимо от того, возникло исключение или нет, и используется для выполнения важных операций, например, очистки ресурсов.

Обратите внимание: блок finally всегда выполняется после try-catch.

try {
    // Код, который может вызвать исключение
} catch (ExceptionType e) {
    // Код обработки исключения
} finally {
    // Код очистки ресурсов
}

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

В Java можно обрабатывать несколько типов исключений, используя несколько catch-блоков — по одному для каждого типа исключения.

try {
    // Код, который может вызвать исключение
} catch (ArithmeticException e) {
    // Обработка исключения ArithmeticException
} catch (ArrayIndexOutOfBoundsException e) {
    // Обработка исключения ArrayIndexOutOfBoundsException
} catch (NumberFormatException e) {
    // Обработка исключения NumberFormatException
}

Как JVM обрабатывает исключения?

Когда возникает исключение, JVM создаёт объект исключения, в котором содержатся имя ошибки, её описание и текущее состояние программы. Этот процесс создания объекта исключения и его обработки во время выполнения называется «генерацией исключения» (throwing an exception). Вместе с этим существует список методов, вызовы которых привели к методу, где возникло исключение. Этот список называется стеком вызовов (call stack). Далее происходит следующее:

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

Exception in thread «abc» Name of Exception : Description
… …… .. // Call Stack

Ниже приведена диаграмма для понимания работы стека вызовов:

Иллюстрация:

class Geeks {

    public static void main(String args[]) {
        // Инициализация пустой строки
        String s = null;

        // Получение длины строки
        System.out.println(s.length());
    }
}

Вывод:

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

Пример:

// Класс, демонстрирующий генерацию исключения
class Geeks {

    // Метод throws Exception (ArithmeticException)
    // Обработчик исключения в самом методе отсутствует
    static int divideByZero(int a, int b) {
        // Эта операция вызовет ArithmeticException (деление на ноль)
        int i = a / b;
        return i;
    }

    // В этом методе тоже отсутствует обработчик,
    // поиск продолжится дальше в стеке вызовов
    static int computeDivision(int a, int b) {
        int res = 0;

        try {
            res = divideByZero(a, b);
        } catch (NumberFormatException ex) {
            // Этот catch не подходит для ArithmeticException
            System.out.println("NumberFormatException is occurred");
        }
        return res;
    }

    // Найден соответствующий обработчик исключения - catch block
    public static void main(String args[]) {
        int a = 1;
        int b = 0;

        try {
            int i = computeDivision(a, b);
        } catch (ArithmeticException ex) {
            // getMessage() выводит описание ошибки (здесь - деление на ноль)
            System.out.println(ex.getMessage());
        }
    }
}

Вывод:

/ by zero

Как программист обрабатывает исключение?

В Java для работы с исключениями используются пять ключевых слов: try, catch, throw, throws и finally.

  • Код, который может вызвать исключение, помещается в блок try.
  • Если возникает исключение, оно перехватывается блоком catch.
  • Исключения можно создавать вручную с помощью throw, а методы обязаны объявлять, какие исключения могут выбрасывать с помощью throws.
  • Блок finally содержит код, который обязательно выполнится после try — независимо от того, было исключение или нет.

Совет: рекомендуется внимательно изучить последовательность выполнения программы при использовании try-catch-finally для полного понимания.

Необходимость try-catch (кастомная обработка исключений)

Рассмотрим следующую программу для лучшего понимания важности использования try-catch для перехвата исключений.

Пример:

// Java программа, демонстрирующая необходимость try-catch
class Geeks {

    public static void main(String[] args) {
        // Объявляем массив размера 4
        int[] arr = new int[4];

        // Следующая инструкция вызовет исключение
        int i = arr[4];

        // Эта строка никогда не выполнится,
        // потому что выше возникло исключение
        System.out.println("Hi, I want to execute");
    }
}

Вывод:

Пояснение: массив объявлен на 4 элемента, следовательно индексы доступны от 0 до 3. При попытке доступа к элементу с индексом 4 возникает исключение. В таком случае JVM абнормально завершает программу. Строка System.out.println(«Hi, I want to execute»); выполнена не будет. Чтобы программа продолжила работу, необходимо обработать исключение с помощью try-catch. Таким образом, для поддержки нормального хода программы нужен блок try-catch.

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

  • Позволяет завершить выполнение программы, даже при возникновении ошибок.
  • Отделяет код, связанный с основной логикой, от кода обработки ошибок, что повышает читаемость.
  • Обеспечивает распространение ошибок по цепочке вызовов методов.
  • Позволяет делать осмысленную отчетность об ошибках.
  • Помогает различать типы ошибок для эффективной их обработки.

🔑 Ключевые моменты:

  • Исключения (Exception) в Java — это непредвиденные ошибки во время выполнения программы, которые нарушают её нормальный ход.

  • Обработка исключений обеспечивает устойчивость приложения, позволяя избежать аварийного завершения и правильно реагировать на ошибки.

  • В Java различают проверяемые (checked) и непроверяемые (unchecked) исключения, а также ошибки (Error), которые обычно не обрабатываются.

  • Ключевые конструкции для обработки исключений: блоки try, catch, finally и операторы throw, throws.

  • Обработка исключений помогает поддерживать чистоту кода и облегчает отладку и сопровождение программ.

Ответить

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