$ sudo teach IT
МОДУЛЬ 9 · УРОК 5

Исключения: try-catch-finally

Что такое исключение, checked vs unchecked, try-catch, finally, try-with-resources, создание своих исключений

~30 минут Для новичков Java

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

Исключение (exception) — это объект, который создаётся при возникновении ошибки во время выполнения программы. Это способ Java сказать: «Что-то пошло не так, и я не могу продолжать нормально».

Представьте, что вы едете на работу. Обычно всё идёт гладко. Но вдруг:

  • Пробка на дороге — вы опаздываете
  • Сломался автобус — нужно искать другой
  • Закрыли метро — планы рушатся

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

Простой пример исключения

public class ExceptionDemo {
    public static void main(String[] args) {
        int a = 10;
        int b = 0;
        int result = a / b;
        System.out.println(result);
    }
}

Вывод программы:

Exception in thread "main" java.lang.ArithmeticException: / by zero
    at ExceptionDemo.main(ExceptionDemo.java:4)

Программа упала! Java создала объект ArithmeticException и вывела сообщение об ошибке. Это unchecked исключение — компилятор не заставляет вас его обрабатывать.

Checked vs Unchecked исключения

Все исключения в Java делятся на два типа:

Характеристика Checked Unchecked
Примеры IOException, SQLException NullPointerException, ArithmeticException
Когда проверяются Компилятором Во время выполнения
Обязательно обрабатывать Да Нет (но рекомендуется)
Иерархия Exception RuntimeException

Пример checked исключения — работа с файлом:

// Это НЕ скомпилируется без try-catch или throws:
import java.io.FileReader;
import java.io.IOException;

public class CheckedDemo {
    public static void main(String[] args) {
        FileReader reader = new FileReader("file.txt");
    }
}

// ОШИБКА КОМПИЛЯЦИИ: Unhandled exception type IOException

Аналогия: Checked exception — это как предупреждение на коробке с лекарством: «Может вызвать сонливость». Вы обязаны это знать и принять меры. Unchecked exception — как солнечный удар: может случиться, но предупредить невозможно.

try-catch

Конструкция try-catch позволяет «поймать» исключение и обработать его, чтобы программа не упала:

public class TryCatchDemo {
    public static void main(String[] args) {
        try {
            int a = 10;
            int b = 0;
            int result = a / b;
            System.out.println(result);
        } catch (ArithmeticException e) {
            System.out.println("Ошибка деления на ноль!");
            System.out.println("Сообщение: " + e.getMessage());
        }

        System.out.println("Программа продолжает работу");
    }
}

Вывод программы:

Ошибка деления на ноль!
Сообщение: / by zero
Программа продолжает работу

Программа не упала! Исключение было «поймано» в блоке catch, и выполнение продолжилось.

Несколько catch-блоков

Вы можете обрабатывать разные типы исключений отдельно:

public class MultiCatchDemo {
    public static void main(String[] args) {
        try {
            String text = null;
            System.out.println(text.length());

            int[] arr = new int[3];
            arr[10] = 5;
        } catch (NullPointerException e) {
            System.out.println("Ссылка null: " + e.getMessage());
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("Индекс за границей: " + e.getMessage());
        } catch (Exception e) {
            System.out.println("Другая ошибка: " + e.getMessage());
        }
    }
}

Вывод программы:

Ссылка null: null

Как только произошло первое исключение (NullPointerException), перешли в соответствующий catch. Второе исключение не было создано.

Важно: Более специфичные исключения должны идти ПЕРЕД общими. Если поставить catch (Exception e) первым — компилятор выдаст ошибку.

multi-catch (Java 7+)

Если обработка для нескольких исключений одинаковая, можно объединить их:

try {
    // код, который может выбросить разные исключения
} catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
    System.out.println("Ошибка индексации: " + e.getMessage());
}

finally

Блок finally выполняется всегда — было исключение или нет. Он используется для освобождения ресурсов:

public class FinallyDemo {
    public static void main(String[] args) {
        System.out.println("Начало");

        try {
            System.out.println("Внутри try");
            int result = 10 / 0;
        } catch (ArithmeticException e) {
            System.out.println("Внутри catch");
        } finally {
            System.out.println("Внутри finally");
        }

        System.out.println("Конец");
    }
}

Вывод программы:

Начало
Внутри try
Внутри catch
Внутри finally
Конец

Блок finally выполняется даже если:

  • В catch произошло новое исключение
  • В try/catch есть return
  • В catch есть return

throws и throw

throw — создать и выбросить исключение вручную:

public static int divide(int a, int b) {
    if (b == 0) {
        throw new ArithmeticException("Деление на 0 запрещено");
    }
    return a / b;
}

throws — объявить, что метод может выбросить исключение:

// Метод объявляет, что может выбросить IOException
public static void readFile(String path) throws java.io.IOException {
    java.io.FileReader reader = new java.io.FileReader(path);
    reader.read();
    reader.close();
}

// Вызывающий код ОБЯЗАН обработать или пробросить дальше:
public static void main(String[] args) {
    try {
        readFile("data.txt");
    } catch (java.io.IOException e) {
        System.out.println("Файл не найден: " + e.getMessage());
    }
}

try-with-resources (Java 7+)

Конструкция try-with-resources автоматически закрывает ресурсы (файлы, соединения и т.д.):

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class TryWithResourcesDemo {
    public static void main(String[] args) {
        // Старый способ:
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new FileReader("file.txt"));
            String line = reader.readLine();
            System.out.println(line);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (reader != null) reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        // Новый способ (Java 7+):
        try (BufferedReader r = new BufferedReader(new FileReader("file.txt"))) {
            String line = r.readLine();
            System.out.println(line);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Ресурсы в скобках автоматически закрываются вызовом метода close() после завершения блока try.

Аналогия: try-with-resources — как автоматическое закрытие зонтика. Вы его открываете (try), пользуетесь, а потом он сам закрывается (finally). Даже если вас облили водой — зонтик всё равно закроется.

Свои исключения

Вы можете создавать собственные исключения, наследуя от Exception (checked) или RuntimeException (unchecked):

// Custom checked exception
public class InsufficientFundsException extends Exception {
    private double deficit;

    public InsufficientFundsException(double deficit) {
        super("Недостаточно средств. Не хватает: " + deficit);
        this.deficit = deficit;
    }

    public double getDeficit() {
        return deficit;
    }
}

// Custom unchecked exception
public class InvalidAgeException extends RuntimeException {
    public InvalidAgeException(String message) {
        super(message);
    }
}

// Использование:
public class BankAccount {
    private double balance;

    public BankAccount(double balance) {
        this.balance = balance;
    }

    public void withdraw(double amount) throws InsufficientFundsException {
        if (amount > balance) {
            throw new InsufficientFundsException(amount - balance);
        }
        balance -= amount;
    }

    public String setAge(int age) {
        if (age < 0 || age > 150) {
            throw new InvalidAgeException("Невозможный возраст: " + age);
        }
        return "Возраст установлен: " + age;
    }
}

// Обработка:
public class CustomExceptionDemo {
    public static void main(String[] args) {
        BankAccount account = new BankAccount(1000);

        try {
            account.withdraw(500);
            System.out.println("Снятие успешно");
            account.withdraw(600);
        } catch (InsufficientFundsException e) {
            System.out.println(e.getMessage());
            System.out.printf("Не хватает: %.2f%n", e.getDeficit());
        }

        try {
            System.out.println(account.setAge(200));
        } catch (InvalidAgeException e) {
            System.out.println(e.getMessage());
        }
    }
}

Вывод программы:

Снятие успешно
Недостаточно средств. Не хватает: 100.0
Не хватает: 100.0
Невозможный возраст: 200

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

Все исключения наследуют от Throwable:

Throwable
├── Error (серьёзные системные ошибки, не обрабатываются)
│   ├── OutOfMemoryError
│   ├── StackOverflowError
│   └── ...
└── Exception
    ├── RuntimeException (unchecked)
    │   ├── NullPointerException
    │   ├── ArrayIndexOutOfBoundsException
    │   ├── ArithmeticException
    │   ├── IllegalArgumentException
    │   └── ...
    └── IOException, SQLException и др. (checked)

Практический пример: валидация данных

public class ValidationException extends Exception {
    private final String field;
    private final String value;

    public ValidationException(String field, String value, String message) {
        super(message);
        this.field = field;
        this.value = value;
    }

    public String getField() { return field; }
    public String getValue() { return value; }
}

public class UserValidator {
    public static void validateEmail(String email) throws ValidationException {
        if (email == null || !email.contains("@")) {
            throw new ValidationException("email", email,
                "Email должен содержать @");
        }
    }

    public static void validateAge(int age) throws ValidationException {
        if (age < 0 || age > 150) {
            throw new ValidationException("age", String.valueOf(age),
                "Возраст должен быть от 0 до 150");
        }
    }

    public static void validateName(String name) throws ValidationException {
        if (name == null || name.trim().isEmpty()) {
            throw new ValidationException("name", name,
                "Имя не может быть пустым");
        }
    }

    public static void main(String[] args) {
        try {
            validateName("Алиса");
            validateEmail("alice@mail.ru");
            validateAge(25);
            System.out.println("Все данные корректны!");
        } catch (ValidationException e) {
            System.out.printf("Ошибка в поле '%s': %s%n",
                e.getField(), e.getMessage());
        }
    }
}

Вывод программы:

Все данные корректны!

Итоги урока

Итоги урока

  • Исключение — объект, сигнализирующий об ошибке
  • Checked исключения — компилятор требует обработки
  • Unchecked исключения (RuntimeException) — обработка не обязательна
  • try-catch — перехват и обработка исключений
  • finally — блок, выполняемый всегда
  • throw — выбросить исключение
  • throws — объявить возможные исключения
  • try-with-resources — автоматическое закрытие ресурсов
  • Свои исключения — наследуйте от Exception или RuntimeException

Следующий урок: Generics →

Тест по исключениям (try-catch-finally)

5 вопросов

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

Premium