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

try-with-resources

Некоторые вещи, которые открывает программа, обязательно нужно закрыть: файл, соединение с базой данных, сетевое подключение. Незакрытый файл держит занятыми ресурсы системы, а записанное в него может так и не попасть на диск. Закрывать такие вещи можно в finally, но этот код длинный, и его часто забывают написать.

Вспомните дверь из урока о finally. Теперь представьте дверь с доводчиком — пружиной, которая закрывает её сама. Вы проходите, а дверь закрывается без вашей помощи. Java умеет работать с такими вещами так же: особый try закрывает их сам.

JAVA
public class Main {
    public static void main(String[] args) {
        try (Door door = new Door("A")) {
            System.out.println("inside " + door.name());
        }
        System.out.println("after");
    }
}

record Door(String name) implements AutoCloseable {
    @Override
    public void close() {
        System.out.println("close " + name);
    }
}

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

TEXT
inside A
close A
after

Ресурс и AutoCloseable

Ресурс — объект, который после работы нужно закрыть. Давайте разберём программу по шагам:

  1. record Door(String name) implements AutoCloseable — наша дверь-ресурс. AutoCloseable — интерфейс из стандартной библиотеки с одним методом close(), а Door его реализует.
  2. try (Door door = new Door("A")) — ресурс объявлен в круглых скобках сразу после try.
  3. Тело блока печатает inside A.
  4. На выходе из блока Java сама вызывает door.close(), и печатается close A.
  5. Затем выполняется код после try и печатает after.

try-with-resources — это try с ресурсами в скобках: при выходе из блока он вызывает close() у каждого из них. Компилятор превращает такой try в try и finally с вызовом close() — тот самый код, который пришлось бы писать вручную.

Ресурсом может быть только объект класса, который реализует AutoCloseable. В интерфейсе метод close() объявлен с throws Exception. Наша Door ничего не бросает и throws не пишет, поэтому catch здесь не нужен.

На собеседовании спрашивают: что такое try-with-resources и что нужно ресурсу? Ответ: блок, который сам закрывает ресурсы при любом выходе, а ресурс должен реализовать AutoCloseable.

Несколько ресурсов

Ресурсы в скобках разделяют точкой с запятой:

JAVA
try (Door a = new Door("A"); Door b = new Door("B")) {
    System.out.println("inside " + a.name() + b.name());
}
TEXT
inside AB
close B
close A

Ресурсы закрываются в обратном порядке: последний открытый — первым. Так и с дверьми: вы вошли через A, потом через B, а на выходе сначала проходите B. Поэтому ресурс, открытый позже, может пользоваться открытым раньше: пока он закрывается, тот ещё работает.

Исключение внутри try

Ресурс закрывается при любом выходе из блока, в том числе по исключению:

JAVA
try (Door door = new Door("C")) {
    System.out.println("inside " + door.name());
    Integer.parseInt("x");
} catch (NumberFormatException e) {
    System.out.println("caught");
}
TEXT
inside C
close C
caught

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

Здесь картинка с доводчиком неточна. Настоящая дверь закрывается сразу, как только вы прошли. Ресурс закрывается только в конце всего блока try, а внутри блока с ним можно работать.

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

Метод close() у двери написали, а интерфейс забыли:

JAVA
record Door(String name) { // ✗
    public void close() {
        System.out.println("close " + name);
    }
}
TEXT
error: incompatible types: try-with-resources not applicable to variable type

Программа с try (Door door = new Door("A")) не компилируется, хотя метод close() у двери есть. Компилятор смотрит не на метод, а на тип: в скобки try можно поставить только AutoCloseable. Ошибку он показывает в строке с try и добавляет Door cannot be converted to AutoCloseable, а исправлять нужно заголовок записи: record Door(String name) implements AutoCloseable.

Главное

  • try-with-resources сам вызывает close() у каждого ресурса при любом выходе из блока.
  • Ресурс — объект класса, который реализует AutoCloseable.
  • Несколько ресурсов закрываются в обратном порядке: последний открытый — первым.
  • При исключении ресурсы закрываются раньше, чем выполняется catch.