Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

try-with-resources

Ресурс — это объект, который держит что-то внешнее: открытый файл, сетевое соединение, подключение к базе данных. Такие объекты нужно закрывать, иначе они остаются занятыми. Конструкция try-with-resources закрывает ресурсы автоматически — даже если внутри произошло исключение.

Чтение файла

Ресурсы объявляют в круглых скобках после try. Когда блок заканчивается, Java сама вызывает у них метод close(). В примере программа сначала создаёт файл, а потом читает его построчно через BufferedReader:

void main() throws IOException {
    Files.writeString(Path.of("todo.txt"), "Купить хлеб\nПозвонить Олегу\n");
 
    try (BufferedReader reader = Files.newBufferedReader(Path.of("todo.txt"))) {
        String line;
        while ((line = reader.readLine()) != null) {
            IO.println("- " + line);
        }
    }
}

Результат:

- Купить хлеб
- Позвонить Олегу

Писать finally с reader.close() не нужно: файл закроется при любом исходе.

AutoCloseable

В скобках try может стоять любой объект, реализующий интерфейс AutoCloseable — с единственным методом close(). Его реализуют файлы, потоки ввода-вывода, соединения с базой данных. Можно реализовать и в своём классе, чтобы увидеть, когда происходит закрытие:

class Connection implements AutoCloseable {
    Connection() {
        IO.println("Соединение открыто");
    }
 
    void send(String message) {
        IO.println("Отправлено: " + message);
    }
 
    @Override
    public void close() {
        IO.println("Соединение закрыто");
    }
}
 
void main() {
    try (Connection connection = new Connection()) {
        connection.send("Привет");
    }
    IO.println("Готово");
}

Результат:

Соединение открыто
Отправлено: Привет
Соединение закрыто
Готово

Закрытие при исключении

Главное преимущество — ресурс закрывается и при ошибке. Причём close() вызывается до того, как сработает catch:

class Connection implements AutoCloseable {
    @Override
    public void close() {
        IO.println("Соединение закрыто");
    }
}
 
void main() {
    try (Connection connection = new Connection()) {
        IO.println("Работаем");
        throw new IllegalStateException("Сбой сети");
    } catch (IllegalStateException e) {
        IO.println("Ошибка: " + e.getMessage());
    }
}

Результат:

Работаем
Соединение закрыто
Ошибка: Сбой сети

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

Ресурсы перечисляют через точку с запятой. Закрываются они в обратном порядке: последний открытый — первым. Так, файл-источник и файл-копия закроются в правильной последовательности:

class Resource implements AutoCloseable {
    private final String name;
 
    Resource(String name) {
        this.name = name;
        IO.println("Открыт " + name);
    }
 
    @Override
    public void close() {
        IO.println("Закрыт " + name);
    }
}
 
void main() {
    try (Resource first = new Resource("A"); Resource second = new Resource("B")) {
        IO.println("Работаем с A и B");
    }
}

Результат:

Открыт A
Открыт B
Работаем с A и B
Закрыт B
Закрыт A

Подавленные исключения

Что, если исключение выбросил и блок try, и метод close()? Главным остаётся исключение из try, а второе не теряется: оно прикрепляется как подавленное (suppressed) и доступно через getSuppressed():

class Faulty implements AutoCloseable {
    @Override
    public void close() {
        throw new IllegalStateException("ошибка при закрытии");
    }
}
 
void main() {
    try (Faulty resource = new Faulty()) {
        throw new RuntimeException("ошибка в работе");
    } catch (RuntimeException e) {
        IO.println("Главная: " + e.getMessage());
        IO.println("Подавленная: " + e.getSuppressed()[0].getMessage());
    }
}

Результат:

Главная: ошибка в работе
Подавленная: ошибка при закрытии

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

Переменная ресурса существует только внутри блока try. Попытка обратиться к ней после блока — ошибка компиляции, ведь к этому моменту ресурс уже закрыт:

void main() throws IOException {
    Files.writeString(Path.of("data.txt"), "1\n2\n");
 
    try (BufferedReader reader = Files.newBufferedReader(Path.of("data.txt"))) {
        IO.println(reader.readLine());
    }
    IO.println(reader.readLine());
}

Результат:

Main.java:7: error: cannot find symbol
    IO.println(reader.readLine());
               ^
  symbol:   variable reader
  location: class Main
1 error
error: compilation failed

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

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.