Пользовательские исключения

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

Создание пользовательского исключения сводится к наследованию от одного из классов:

  • ``Exception`` — для checked-исключений, которые вызывающий код обязан обработать.

  • ``RuntimeException`` — для unchecked-исключений, которые обычно сигнализируют о баге в коде (нарушение контракта, неверные аргументы).

Имя класса по соглашению заканчивается на Exception. Полезно предоставить несколько конструкторов, повторяющих сигнатуры родителя.

Синтаксис

public class SensorException extends Exception {
    public SensorException(String message) {
        super(message);
    }
    public SensorException(String message, Throwable cause) {
        super(message, cause);
    }
}

Пример 1. Checked-исключение

class SensorTimeoutException extends Exception {
    public SensorTimeoutException(String message) {
        super(message);
    }
}

public class CheckedDemo {
    static int readDistance() throws SensorTimeoutException {
        throw new SensorTimeoutException("RCWL-9610A не ответил за 1000 мс");
    }

    public static void main(String[] args) {
        try {
            readDistance();
        } catch (SensorTimeoutException e) {
            System.out.println("Ошибка: " + e.getMessage());
        }
    }
}

Output:

Ошибка: RCWL-9610A не ответил за 1000 мс

Пример 2. Unchecked-исключение

class InvalidPinException extends RuntimeException {
    public InvalidPinException(int pin) {
        super("Недопустимый пин: " + pin);
    }
}

public class UncheckedDemo {
    static void setupPin(int pin) {
        if (pin < 0 || pin > 53) {
            throw new InvalidPinException(pin);
        }
        System.out.println("Пин " + pin + " готов");
    }

    public static void main(String[] args) {
        setupPin(13);
        try {
            setupPin(100);
        } catch (InvalidPinException e) {
            System.out.println(e.getMessage());
        }
    }
}

Output:

Пин 13 готов
Недопустимый пин: 100

Пример 3. Передача дополнительных данных

class SensorReadException extends Exception {
    private final String sensorName;
    private final int errorCode;

    public SensorReadException(String sensorName, int errorCode, String message) {
        super(message);
        this.sensorName = sensorName;
        this.errorCode = errorCode;
    }

    public String getSensorName() { return sensorName; }
    public int getErrorCode()     { return errorCode; }
}

public class CustomDataDemo {
    public static void main(String[] args) {
        try {
            throw new SensorReadException("DHT22", 42, "CRC mismatch");
        } catch (SensorReadException e) {
            System.out.println("Датчик: " + e.getSensorName());
            System.out.println("Код:    " + e.getErrorCode());
            System.out.println("Текст:  " + e.getMessage());
        }
    }
}

Output:

Датчик: DHT22
Код:    42
Текст:  CRC mismatch

Пример 4. Цепочка с cause

class ConfigLoadException extends Exception {
    public ConfigLoadException(String message, Throwable cause) {
        super(message, cause);
    }
}

public class ChainDemo {
    static void loadConfig() throws ConfigLoadException {
        try {
            Integer.parseInt("bad");
        } catch (NumberFormatException e) {
            throw new ConfigLoadException("Не удалось загрузить config.ini", e);
        }
    }

    public static void main(String[] args) {
        try {
            loadConfig();
        } catch (ConfigLoadException e) {
            System.out.println(e.getMessage());
            System.out.println("Причина: " + e.getCause().getClass().getSimpleName());
        }
    }
}

Output:

Не удалось загрузить config.ini
Причина: NumberFormatException

Пример 5. Иерархия пользовательских исключений

class DeviceException extends Exception {
    public DeviceException(String msg) { super(msg); }
}
class MotorException extends DeviceException {
    public MotorException(String msg) { super(msg); }
}
class ServoException extends DeviceException {
    public ServoException(String msg) { super(msg); }
}

public class HierarchyDemo {
    static void runDevice(int id) throws DeviceException {
        if (id == 1) throw new MotorException("Мотор заклинило");
        if (id == 2) throw new ServoException("Серво не отвечает");
    }

    public static void main(String[] args) {
        for (int i = 1; i <= 2; i++) {
            try {
                runDevice(i);
            } catch (DeviceException e) {
                System.out.println(e.getClass().getSimpleName() + ": " + e.getMessage());
            }
        }
    }
}

Output:

MotorException: Мотор заклинило
ServoException: Серво не отвечает

Подводные камни

Предупреждение

  • Не наследуйтесь от ``Throwable`` или ``Error`` напрямую — это нарушает соглашения.

  • Для checked-исключения добавьте поле serialVersionUID, если класс будет сериализован.

  • Избегайте слишком общих исключений — конкретный тип помогает в отладке.

  • Конструктор должен вызывать ``super(message)`` — иначе getMessage() вернёт null.

  • Не создавайте исключения там, где достаточно вернуть результат — это дорого по производительности из-за заполнения stack trace.

См. также

Примечание

Материал подготовлен на основе официальной документации Oracle Java Tutorials — Creating Exception Classes (Oracle Free Documentation License). Текст переведён и переработан, примеры кода написаны заново.