Статьи

Полный курс по языку Java — от установки JDK до многопоточности и HTTP-запросов

Исключения

Страница 19 из 30 · 2026-10-25 · 5 мин чтения

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

Исключение — объект, описывающий ошибку во время выполнения. Если его не обработать, программа завершится со stack trace.

public class Crash {    public static void main(String[] args) {        int[] data = new int[3];        System.out.println(data[5]);    }}
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 3    at Crash.main(Crash.java:4)

Иерархия

Throwable├── Error                  (серьезные сбои JVM: OutOfMemoryError, StackOverflowError)└── Exception    ├── IOException        (checked)    ├── SQLException       (checked)    └── RuntimeException   (unchecked)        ├── NullPointerException        ├── IllegalArgumentException        ├── IllegalStateException        ├── ArrayIndexOutOfBoundsException        ├── ClassCastException        ├── NumberFormatException        └── ArithmeticException
  • Checked — компилятор требует обработать или объявить через throws. Обычно внешние ошибки: файл, сеть, база.
  • Unchecked (RuntimeException и наследники) — обычно ошибки программиста.
  • Error — ловить не нужно.

try-catch

public class TryCatch {    public static void main(String[] args) {        try {            int result = 10 / 0;            System.out.println(result);        } catch (ArithmeticException e) {            System.out.println("Деление на ноль: " + e.getMessage());        }        System.out.println("Программа продолжает работу");    }}

Несколько catch

Более конкретные исключения указываются раньше общих:

public class MultiCatch {    public static void main(String[] args) {        String[] inputs = {"42", "abc", null};        for (String input : inputs) {            try {                int value = Integer.parseInt(input);                System.out.println("Число: " + value);            } catch (NumberFormatException e) {                System.out.println("Не число: " + input);            } catch (RuntimeException e) {                System.out.println("Другая ошибка: " + e);            }        }    }}

Объединение в одном catch:

try {    riskyOperation();} catch (IllegalArgumentException | IllegalStateException e) {    System.out.println(e.getMessage());}

finally

Блок finally выполняется всегда, независимо от того, было ли исключение:

public class Finally {    static int test() {        try {            return 1;        } finally {            System.out.println("finally выполнен");        }    }    public static void main(String[] args) {        System.out.println(test());    }}

throw и throws

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

import java.io.IOException;public class ThrowDemo {    static void checkAge(int age) {        if (age < 0) {            throw new IllegalArgumentException("Возраст не может быть отрицательным");        }    }    static void readConfig(String path) throws IOException {        if (path.isEmpty()) {            throw new IOException("Пустой путь");        }    }    public static void main(String[] args) {        try {            checkAge(-5);        } catch (IllegalArgumentException e) {            System.out.println(e.getMessage());        }        try {            readConfig("");        } catch (IOException e) {            System.out.println("IO: " + e.getMessage());        }    }}

try-with-resources

Ресурсы, реализующие AutoCloseable, закрываются автоматически, даже при ошибке:

import java.io.BufferedReader;import java.io.FileReader;import java.io.IOException;public class ReadFile {    public static void main(String[] args) {        try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {            String line;            while ((line = reader.readLine()) != null) {                System.out.println(line);            }        } catch (IOException e) {            System.out.println("Ошибка чтения: " + e.getMessage());        }    }}

Собственный ресурс:

public class Resource implements AutoCloseable {    private final String name;    public Resource(String name) {        this.name = name;        System.out.println("Открыт " + name);    }    @Override    public void close() {        System.out.println("Закрыт " + name);    }    public static void main(String[] args) {        try (Resource a = new Resource("A"); Resource b = new Resource("B")) {            System.out.println("Работа");        }    }}

Вывод: Открыт A, Открыт B, Работа, Закрыт B, Закрыт A. Закрытие идет в обратном порядке.

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

public class InsufficientFundsException extends Exception {    private final double shortage;    public InsufficientFundsException(String message, double shortage) {        super(message);        this.shortage = shortage;    }    public double getShortage() {        return shortage;    }}
public class Wallet {    private double balance;    public Wallet(double balance) {        this.balance = balance;    }    public void pay(double amount) throws InsufficientFundsException {        if (amount > balance) {            throw new InsufficientFundsException("Не хватает средств", amount - balance);        }        balance -= amount;    }    public static void main(String[] args) {        Wallet wallet = new Wallet(100);        try {            wallet.pay(150);        } catch (InsufficientFundsException e) {            System.out.println(e.getMessage() + ": " + e.getShortage());        }    }}

Для ошибок программиста наследуйтесь от RuntimeException.

Цепочки исключений

Сохраняйте исходную причину:

public class ServiceException extends RuntimeException {    public ServiceException(String message, Throwable cause) {        super(message, cause);    }}
public class Chain {    static void load() {        try {            Integer.parseInt("xyz");        } catch (NumberFormatException e) {            throw new ServiceException("Не удалось загрузить настройки", e);        }    }    public static void main(String[] args) {        try {            load();        } catch (ServiceException e) {            System.out.println(e.getMessage());            System.out.println("Причина: " + e.getCause());        }    }}

Хорошие практики

  • Ловите конкретные исключения, а не Exception и тем более Throwable.
  • Не оставляйте пустой catch: ошибка пропадет бесследно.
  • Не используйте исключения для обычного управления потоком.
  • Проверяйте аргументы в начале метода и бросайте IllegalArgumentException.
  • Сообщение исключения должно содержать данные, помогающие найти проблему.
  • Логируйте исключение целиком, а не только getMessage().

NullPointerException

Самая частая ошибка. Способы защиты:

import java.util.Objects;import java.util.Optional;public class NullSafety {    static String describe(String text) {        Objects.requireNonNull(text, "text не должен быть null");        return text.trim();    }    static Optional<String> find(String key) {        if ("a".equals(key)) {            return Optional.of("значение");        }        return Optional.empty();    }    public static void main(String[] args) {        System.out.println(find("a").orElse("нет"));        System.out.println(find("b").orElse("нет"));        System.out.println(find("a").map(String::toUpperCase).orElse(""));    }}

Приемы:

  • сравнивайте константу с переменной: "yes".equals(answer);
  • возвращайте пустую коллекцию вместо null;
  • используйте Optional в возвращаемых значениях, но не в полях и параметрах.