Перейти к содержимому
Теория · Исключения · урок 4 из 12

finally

Вернёмся в здание. Кто бы ни уходил с этажа — спокойно после работы, по тревоге или раньше времени, — за ним закрывается дверь. Она закрывается всегда, как бы человек ни вышел.

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

JAVA
public class Main {
    public static void main(String[] args) {
        int[] boxes = {2, 0};
        for (int n : boxes) {
            try {
                System.out.println(8 / n);
            } catch (ArithmeticException e) {
                System.out.println("zero");
            } finally {
                System.out.println("door closed");
            }
        }
    }
}

Программа печатает:

TEXT
4
door closed
zero
door closed

Три пути из try

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

Без исключения

try выполняется до конца, catch пропускается, затем выполняется finally. При n, равном 2, программа печатает 4, а потом door closed.

Исключение поймали

try прерывается на строке с исключением, выполняется подходящий catch, а за ним finally. При n, равном 0, печатается zero, а потом снова door closed.

Исключение никто не поймал

Подходящего catch нет — или catch нет вовсе: try можно написать с одним finally. Тогда finally всё равно выполняется, а исключение после него продолжает путь к вызвавшему методу.

finally и return

Дверь закрывается и тогда, когда метод уходит раньше времени через return:

JAVA
public class Main {
    static int share(int n) {
        try {
            return 10 / n;
        } finally {
            System.out.println("door closed");
        }
    }

    public static void main(String[] args) {
        System.out.println(share(2));
        System.out.println(share(0));
    }
}
TEXT
door closed
5
door closed
Exception in thread "main" java.lang.ArithmeticException: / by zero

Давайте разберём по шагам:

  1. share(2) вычисляет 10 / 2 для return. Но прежде чем метод вернёт 5, выполняется finally и печатает door closed.
  2. main получает 5 и печатает его.
  3. share(0): деление бросает исключение. catch нет, но finally всё равно печатает door closed.
  4. Исключение уходит из share в main, там его тоже никто не ловит, и программа останавливается.

Это классический вопрос на собеседовании: выполнится ли finally, если в try стоит return? Да. Значение для return уже вычислено, затем выполняется finally, и только потом метод возвращает результат. Не выполнится finally, если остановится сама JVM: например, если выключить компьютер.

Здесь картинка с дверью неточна. В здании дверь закрывается, когда вы уже вышли. А finally выполняется, пока метод ещё не закончился: main ждёт результат 5, а share в это время печатает door closed.

Частая ошибка

Код, который должен выполниться всегда, поставили после try и catch, а не в finally:

JAVA
String s = "0";
try {
    System.out.println(10 / Integer.parseInt(s));
} catch (NumberFormatException e) {
    System.out.println("not a number");
}
System.out.println("door closed"); // ✗
TEXT
Exception in thread "main" java.lang.ArithmeticException: / by zero

Строку "0" прочитать можно, но на ноль делить нельзя. catch ловит только NumberFormatException, а ArithmeticException уходит дальше, и строка после try не выполняется: дверь осталась открытой. Перенесите её в блок finally после catch. Тогда door closed напечатается и здесь, перед тем как программа остановится.

Главное

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