Исключения ·
‹ Previous Next ›
⏱ 5 min read Modified: 2026-09-25

Оператор try-catch-finally в Java

Поставьте catch (Exception e) перед catch (ArithmeticException e) — и программа даже не скомпилируется: компилятор сообщит, что ArithmeticException уже перехвачен. Порядок блоков catch, правила multi-catch и поведение finally при return — частые темы вопросов на собеседованиях по Java.

Конструкция try-catch-finally в Java — это механизм обработки исключений: в блоке try размещается код, который может выбросить исключение, блоки catch перехватывают и обрабатывают исключения указанных типов, а блок finally выполняется после try и catch практически всегда — обычно в нём освобождают ресурсы.

Синтаксис try-catch-finally в Java

Общая форма блока обработки исключений:

public class ExceptionExample1 {
    public static void main(String[] args) {
        try {
            // блок кода, в котором отслеживаются ошибки
        } catch (ExceptionType1 e) {
            // обработчик исключений типа ExceptionType1
        } catch (ExceptionType2 e) {
            // обработчик исключений типа ExceptionType2
        } finally {
            // блок кода, который выполняется после завершения try и catch
        }
    }
}

Блоки catch и finally необязательны по отдельности, но блок try не может стоять один. Допустимые комбинации:

  • try-catch — перехват и обработка исключения;
  • try-finally — без обработки, но с гарантированным выполнением завершающего кода;
  • try-catch-finally — обработка плюс завершающий код;
  • try-with-resources (Java 7+) — может обходиться без catch и finally, ресурсы закрываются автоматически.

Одиночный try { ... } без catch, finally и ресурсов — ошибка компиляции.

Несколько блоков catch и их порядок

Один фрагмент кода может выбросить исключения разных типов. Для этого после try пишут два или более блока catch, каждый — для своего типа исключений.

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

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

Рассмотрим пример применения нескольких блоков catch:

public class MultiCatch {
    public static void main(String[] args) {
        try {
            int a = args.length;
            int b = 42 / a;
            int[] c = new int[a];
            c[a] = 6;
        } catch (ArithmeticException e) {
            System.out.println("Деление на ноль: " + e);
        } catch (ArrayIndexOutOfBoundsException e) {
            e.printStackTrace();
        }
        System.out.println("Уже после блока try-catch-catch.");
    }
}

Если запустить программу без аргументов, то a = 0 и деление 42 / a выбросит ArithmeticException. Если передать один аргумент, деление пройдёт, но обращение c[1] к массиву длиной 1 выбросит ArrayIndexOutOfBoundsException. В обоих случаях после обработки выполнится последняя строка main.

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

public class SuperSubCatch {
    public static void main(String[] args) {
        try {
            int a = 0;
            int b = 42 / a;
        } catch (Exception e) {
            System.out.println("Перехват исключений общего класса Exception.");
        } /*catch (ArithmeticException e) {
            // ОШИБКА компиляции: exception ArithmeticException has already been caught
            System.out.println("Этот код вообще недостижим.");
        }*/
    }
}

Ещё одно правило компилятора: нельзя писать catch для проверяемого исключения (например, IOException), которое код в блоке try выбросить не может, — это тоже ошибка компиляции. На непроверяемые исключения, а также на Exception и Throwable это правило не распространяется.

Multi-catch: несколько исключений в одном catch

Начиная с Java 7, несколько типов исключений, которые обрабатываются одинаково, можно перечислить в одном блоке catch через вертикальную черту |. Такой блок называют multi-catch.

До Java 7 приходилось дублировать обработчики:

try {
    // ...
} catch (IllegalStateException e) {
    System.out.println(e.getMessage());
} catch (IOException e) {
    System.out.println(e.getMessage());
} catch (SQLException e) {
    System.out.println(e.getMessage());
}

С multi-catch тот же код записывается короче:

try {
    // ...
} catch (IllegalStateException | IOException | SQLException e) {
    System.out.println(e.getMessage());
}

Пример использования multi-catch блока:

public class MultiCatch2 {
    public static void main(String[] args) {
        try {
            int a = args.length;
            int b = 42 / a;
            int[] c = new int[a];
            c[a] = 6;
        } catch (ArithmeticException | ArrayIndexOutOfBoundsException e) {
            System.out.println("Ошибка: " + e);
        }
        System.out.println("Уже после блока try-catch.");
    }
}

Типы в multi-catch не должны быть связаны наследованием: если один тип является подклассом другого, код не скомпилируется. Например, catch (FileNotFoundException | IOException e) — ошибка, потому что FileNotFoundException наследует IOException, и достаточно указать только IOException.

Обратите внимание

Параметр multi-catch блока неявно объявлен как final: присвоить ему новое значение (e = new IOException();) нельзя — ошибка компиляции. В обычном catch с одним типом параметр переприсвоить можно, если он не объявлен как final явно.

Блок finally и конструкция try-finally

Когда генерируется исключение, выполнение метода идёт по нелинейному пути. Если файл открывается в начале метода и закрывается в конце, вряд ли кого-то устроит, что код закрытия файла будет пропущен из-за исключения. Для таких случаев и служит блок finally.

Блок finally выполняется по завершении блоков try и catch, но перед кодом, который следует за всей конструкцией.

Блок finally выполняется независимо от того, было ли выброшено исключение. Если исключение выброшено и ни один из блоков catch ему не соответствует, finally всё равно выполнится, а затем исключение пойдёт дальше по стеку вызовов. Каждый раз, когда метод собирается вернуть управление вызывающему коду из блока try-catch — через необработанное исключение или явно через оператор return, — блок finally выполняется перед выходом из метода.

В примере ниже все три метода используют конструкцию try-finally без catch: исключение в procA() не обрабатывается на месте, а перехватывается уже в main, но finally успевает выполниться.

public class FinallyUse {
    // Выход из метода через исключение
    public static void procA() {
        try {
            System.out.println("Внутри procA");
            throw new RuntimeException("demo");
        } finally {
            System.out.println("finally для procA");
        }
    }

    // Возврат изнутри try-блока
    public static void procB() {
        try {
            System.out.println("Внутри procB");
            return;
        } finally {
            System.out.println("finally для procB");
        }
    }

    // Нормальное выполнение try-блока
    public static void procC() {
        try {
            System.out.println("Внутри procC");
        } finally {
            System.out.println("finally для procC");
        }
    }

    public static void main(String[] args) {
        try {
            procA();
        } catch (Exception e) {
            System.out.println("Исключение выброшено");
        }
        procB();
        procC();
    }
}

Результат выполнения:

Внутри procA
finally для procA
Исключение выброшено
Внутри procB
finally для procB
Внутри procC
finally для procC

Для закрытия файлов, соединений и потоков в современном коде вместо ручного finally обычно используют try-with-resources (Java 7+): ресурсы, реализующие AutoCloseable, закрываются автоматически, а исключения при закрытии не теряются.

Когда finally выполняется, а когда нет

Ситуация Выполнится ли finally Комментарий
try завершился без исключений Да Сразу после try
Исключение перехвачено в catch Да После выполнения catch
Исключение не перехвачено ни одним catch Да Затем исключение передаётся вызывающему методу
Новое исключение выброшено внутри catch Да Затем новое исключение передаётся дальше
return, break или continue в try/catch Да Перед фактическим выходом
Вызов System.exit() в try/catch Нет JVM завершает работу немедленно
Крах JVM, принудительное завершение процесса Нет Например, kill -9 или отключение питания
Бесконечный цикл или вечная блокировка в try Нет Управление просто не доходит до finally
Daemon-поток, а JVM завершается Не гарантируется Daemon-потоки останавливаются без выполнения finally

Неочевидные моменты: return в finally

Если return есть и в try, и в finally, метод вернёт значение из finally — оно перекрывает результат блока try:

public class FinallyReturn {
    static int getValue() {
        try {
            return 1;
        } finally {
            return 2; // перекрывает return из try
        }
    }

    static int lostException() {
        try {
            throw new RuntimeException("Ошибка");
        } finally {
            return 0; // исключение молча теряется
        }
    }

    public static void main(String[] args) {
        System.out.println(getValue());      // 2
        System.out.println(lostException()); // 0, исключения нет
    }
}

Во втором методе исключение вообще «исчезает»: return в finally отменяет его распространение, и вызывающий код о проблеме не узнает.

Важно

Не пишите return, break, continue и throw внутри finally: они подменяют результат метода и могут скрыть исключение. Компилятор это разрешает, но IDE и статические анализаторы помечают такой код как ошибку проектирования.

Часто задаваемые вопросы

Есть ли в Java блок else для try-catch, как в Python?

Нет, конструкции try-catch-else в Java не существует. Код, который должен выполниться только при отсутствии исключения, просто пишут в конце блока try после потенциально опасных операций. Если же он должен выполняться всегда, его помещают в finally.

Чем multi-catch отличается от catch (Exception e)?

Multi-catch перехватывает только явно перечисленные типы, например catch (IOException | SQLException e), а остальные исключения пропускает дальше. catch (Exception e) перехватывает вообще все проверяемые и непроверяемые исключения, включая ошибки в логике программы, которые лучше не глушить.

Изменит ли finally возвращаемое значение, если поменять в нём переменную?

Для примитивов и неизменяемых объектов — нет. В методе int x = 1; try { return x; } finally { x = 10; } вернётся 1: значение для return вычисляется и запоминается до выполнения finally. Но если вернуть ссылку на изменяемый объект (например, список) и изменить его содержимое в finally, вызывающий код увидит изменения.

Что будет, если исключение выброшено внутри catch?

Остальные блоки catch той же конструкции его не перехватят — они относятся только к блоку try. Сначала выполнится finally, а затем новое исключение уйдёт вызывающему методу. Чтобы не потерять исходную причину, передавайте её в конструктор: throw new MyException("...", e).

Что лучше для закрытия ресурсов: finally или try-with-resources?

Try-with-resources. Он короче, закрывает ресурсы автоматически в обратном порядке и сохраняет исключения, возникшие при закрытии, как suppressed. Ручной finally оправдан, когда нужно выполнить завершающее действие, не связанное с AutoCloseable, например снять блокировку lock.unlock().

Practice

Reinforce this lesson with hands-on tasks, each with a level and a time estimate.

All module tasks →

Comments

Please log in or register to have a possibility to add comment.