«1. Обзор

В этом уроке мы рассмотрим последствия перехвата Throwable.

2. Класс Throwable

В документации по Java класс Throwable определяется как «надкласс всех ошибок и исключений в языке Java».

Давайте посмотрим на иерархию класса Throwable:

Класс Throwable имеет два прямых подкласса, а именно классы Error и Exception.

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

Давайте рассмотрим типы ситуаций, в которых программа может столкнуться при сбое.

3. Восстанавливаемые ситуации

Существуют ситуации, когда восстановление вообще возможно и может быть обработано как проверенными, так и непроверенными подклассами класса Exception.

Например, программа может захотеть использовать файл, который не существует в указанном месте, в результате чего будет выдано проверенное исключение FileNotFoundException.

Другой пример: программа пытается получить доступ к системному ресурсу, не имея на это разрешения, что приводит к возникновению непроверенного исключения AccessControlException.

Согласно документации Java, класс Exception «указывает на условия, которые разумное приложение может захотеть перехватить».

4. Неисправимые ситуации

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

В этих ситуациях JVM выдает соответственно StackOverflowError и OutOfMemoryError. Как следует из их названий, это подклассы класса Error.

Согласно документации Java, класс Error \»указывает на серьезные проблемы, которые разумное приложение не должно пытаться отловить\».

5. Пример восстановимых и невосстановимых ситуаций

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

class StorageAPI {

    public void addIDsToStorage(int capacity, Set<String> storage) throws CapacityException {
        if (capacity < 1) {
            throw new CapacityException("Capacity of less than 1 is not allowed");
        }
        int count = 0;
        while (count < capacity) {
            storage.add(UUID.randomUUID().toString());
            count++;
        }
    }

    // other methods go here ...
}

Несколько потенциальных точек отказа могут возникнуть, когда вызов addIDsToStorage:

    CapacityException — проверенный подкласс Exception при передаче значения емкости менее 1. NullPointerException — непроверенный подкласс Exception, если вместо экземпляра Set указано нулевое значение хранилища. \u003cString\u003e OutOfMemoryError — непроверенный подкласс Error, если JVM исчерпывает память до выхода из цикла while

Ситуации CapacityException и NullPointerException — это сбои, от которых программа может восстановиться, но OutOfMemoryError является неустранимой.

6. Перехват Throwable

Предположим, что пользователь API перехватывает Throwable только в try-catch при вызове addIDsToStorage:

public void add(StorageAPI api, int capacity, Set<String> storage) {
    try {
        api.addIDsToStorage(capacity, storage);
    } catch (Throwable throwable) {
        // do something here
    }
}

Это означает, что вызывающий код реагирует на исправимые и неисправимые ситуации в так же.

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

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

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

7. Заключение

В этой статье мы рассмотрели последствия перехвата Throwable в блоке try-catch.

Как всегда, полный исходный код примера доступен на Github.