Перейти к содержанию
Educora
Продвинутый17 мин10 / 18

Исключения: обработка ошибок

Перехватывай ошибки с помощью try/catch/finally, различай проверяемые и непроверяемые исключения, выбрасывай исключения через throw и создавай собственный класс исключения.

Проверь себя
В этом уроке ты узнаешь
  • Объяснять, что такое исключение, и читать трассировку стека
  • Использовать блоки try, catch и finally
  • Различать проверяемые и непроверяемые исключения и писать собственный класс исключения

Пользователь ввёл «abc» вместо возраста, файл удалён, пропал интернет — в реальных программах не всё идёт по плану. Хорошая программа в таких случаях не «падает», а вежливо сообщает о проблеме и продолжает работу. В Java неожиданные ситуации обрабатывают с помощью исключений (exceptions).

Что такое исключение?

Исключение — это объект, описывающий событие, которое нарушает нормальный ход программы. Когда оно возникает, Java его «выбрасывает» (throw): текущий метод сразу останавливается, и исключение поднимается по цепочке вызовов, пока его кто-нибудь не перехватит. Если никто не перехватит, программа остановится и выведет в консоль трассировку стека (stack trace).

Java
public class Main {
    public static void main(String[] args) {
        int[] marks = {5, 4, 3};
        System.out.println("Start");
        System.out.println(marks[5]);
        System.out.println("End");
    }
}
Ожидаемый результат
Start
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 3
	at Main.main(Main.java:5)

Читай трассировку сверху вниз: сначала тип исключения (ArrayIndexOutOfBoundsException), затем сообщение (индекс 5 выходит за границы массива длиной 3), а потом место — 5-я строка файла Main.java. «End» не напечаталось: после исключения остаток метода не выполняется.

try, catch и finally

Опасный код помещают в блок try. Если возникает исключение, управление переходит в блок catch подходящего типа. Блок finally выполняется в любом случае — было исключение или нет; в нём обычно делают «уборку»: закрывают файл или соединение.

Java
public class Main {
    public static void main(String[] args) {
        String[] inputs = {"42", "abc", "0"};
        for (String input : inputs) {
            try {
                int n = Integer.parseInt(input);
                System.out.println("100 / " + n + " = " + (100 / n));
            } catch (NumberFormatException e) {
                System.out.println("Not a number: " + e.getMessage());
            } catch (ArithmeticException e) {
                System.out.println("Math error: " + e.getMessage());
            } finally {
                System.out.println("-- checked \"" + input + "\"");
            }
        }
    }
}
Ожидаемый результат
100 / 42 = 2
-- checked "42"
Not a number: For input string: "abc"
-- checked "abc"
Math error: / by zero
-- checked "0"
  • Integer.parseInt("abc") не может превратить текст в число и выбрасывает NumberFormatException.
  • Деление целого числа на ноль вызывает ArithmeticException. (Любопытно, что 100.0 / 0 не выбрасывает исключение, а даёт Infinity.)
  • e.getMessage() возвращает сообщение исключения. После исключения программа не остановилась — цикл продолжился со следующим значением.
  • finally сработал во всех трёх случаях.

Проверяемые и непроверяемые исключения

Общий родитель всех исключений — Throwable. От него происходят Error (серьёзные проблемы JVM, например нехватка памяти, — их обычно не перехватывают) и Exception. У Exception есть особая ветвь — RuntimeException. Это деление задаёт уникальное правило Java:

ПризнакПроверяемые (checked)Непроверяемые (unchecked)
Наследуется отException (но не RuntimeException)RuntimeException
Требует ли компилятор?да: перехвати или объяви через throwsнет
Обычно означаетпроблема вне контроля программыошибка программиста
ПримерыIOException, FileNotFoundExceptionNullPointerException, ArithmeticException, IllegalArgumentException

Например, если написать new FileReader("data.txt") без try, код не скомпилируется: unreported exception FileNotFoundException; must be caught or declared to be thrown. Компилятор словно говорит: «Файла может не оказаться — заранее реши, что делать в этом случае».

throw, throws и собственные исключения

Исключение можно выбросить и самому: throw new IllegalArgumentException("..."). Это лучший способ не дать методу принять неверные данные. Чтобы создать свой класс исключения, унаследуй его от Exception (проверяемое) или RuntimeException (непроверяемое). Метод, выбрасывающий проверяемое исключение, должен объявить это в заголовке через throws.

Java
public class Main {
    static void setAge(int age) {
        if (age < 0 || age > 150) {
            throw new IllegalArgumentException("Invalid age: " + age);
        }
        System.out.println("Age set to " + age);
    }

    public static void main(String[] args) {
        try {
            setAge(25);
            setAge(-3);
            setAge(40);
        } catch (IllegalArgumentException e) {
            System.out.println("Error: " + e.getMessage());
        }
        System.out.println("Program continues");
    }
}
Ожидаемый результат
Age set to 25
Error: Invalid age: -3
Program continues

setAge(40) так и не был вызван: как только setAge(-3) выбросил исключение, блок try прервался и управление перешло в catch. А теперь создадим собственное проверяемое исключение — для случая, когда в кошельке не хватает денег:

Java
public class Main {
    public static void main(String[] args) {
        Wallet wallet = new Wallet(30);
        try {
            wallet.pay(12);
            wallet.pay(25);
        } catch (InsufficientFundsException e) {
            System.out.println(e.getMessage());
            System.out.println("Missing: " + e.getMissing() + " AZN");
        }
    }
}

class InsufficientFundsException extends Exception {
    private final double missing;

    InsufficientFundsException(double missing) {
        super("Not enough money in the wallet");
        this.missing = missing;
    }

    double getMissing() { return missing; }
}

class Wallet {
    private double balance;

    Wallet(double balance) { this.balance = balance; }

    void pay(double amount) throws InsufficientFundsException {
        if (amount > balance) {
            throw new InsufficientFundsException(amount - balance);
        }
        balance -= amount;
        System.out.println("Paid " + amount + ", left " + balance);
    }
}
Ожидаемый результат
Paid 12.0, left 18.0
Not enough money in the wallet
Missing: 7.0 AZN

Главное

  • Исключение — событие, нарушающее нормальный ход программы; если его не перехватить, программа остановится и выведет трассировку стека.
  • try содержит опасный код, catch перехватывает исключение определённого типа, а finally выполняется всегда.
  • Проверяемые исключения (IOException) нужно перехватить или объявить через throws; наследники RuntimeException не проверяются.
  • throw new ... выбрасывает исключение; для собственного исключения наследуйся от Exception или RuntimeException.
  • Не пиши пустой блок catch — информация об ошибке теряется.

Проверь себя

Вопросов: 10. Каждый правильный ответ приносит XP.

1 / 10
Когда выполняется блок finally?