Java try-with-resources
Автоматически закрывайте AutoCloseable-ресурсы в Java с помощью оператора try-with-resources.
Оператор try-with-resources — это решение Java для паттерна «открыл, использовал, всегда закрой». Вы объявляете ресурс в начале блока try, и JVM гарантирует его закрытие при выходе из блока — независимо от того, произошёл ли нормальный возврат, было ли выброшено исключение или блок был прерван каким-либо другим управляющим потоком. Многословный танец с finally сворачивается в одно объявление.
В этой главе рассматриваются синтаксис, что считается ресурсом, порядок закрытия нескольких ресурсов, механизм подавленных исключений, делающий эту возможность надёжной, и случаи, когда данный оператор применять не следует.
Синтаксис
try (FileReader reader = new FileReader(path)) {
// use reader
}Вот и всё. Никакого finally, никакого явного close(). При выходе из try JVM автоматически вызывает reader.close(). Блоки catch и finally, если вы их добавите, по-прежнему работают — они выполняются после закрытия ресурса.
try (FileReader reader = new FileReader(path)) {
// use reader
} catch (IOException e) {
// handle — at this point the reader is already closed
}Что может быть ресурсом
Ресурс должен реализовывать AutoCloseable (или его более старый подинтерфейс Closeable). Этот интерфейс объявляет единственный метод:
public interface AutoCloseable {
void close() throws Exception;
}Большинство типов, к которым вы обратитесь, уже реализуют его: InputStream, OutputStream, Reader, Writer, Connection, Statement, ResultSet, Scanner, обёртки Lock и многие сторонние клиенты. Если вы пишете класс, владеющий ресурсом, реализуйте AutoCloseable самостоятельно.
Несколько ресурсов
Объявления разделяются точкой с запятой. Ресурсы закрываются в обратном порядке объявления — последний открытый закрывается первым:
try (
FileInputStream in = new FileInputStream(src);
FileOutputStream out = new FileOutputStream(dst)
) {
in.transferTo(out);
}
// closes `out` first, then `in`Обратный порядок важен, потому что второй ресурс нередко зависит от первого. Закрытие зависимого ресурса первым оставляет фундамент нетронутым ещё на мгновение — это более безопасный порядок.
Повторное использование существующих ресурсов (Java 9+)
Если у вас уже есть переменная, хранящая AutoCloseable, объявлять новую не нужно — передайте её по имени:
BufferedReader reader = openSomewhere();
try (reader) {
// use it
}Переменная должна быть final или effectively final (её нельзя переприсваивать). Эта форма удобна, когда ресурс создаётся в другом месте — ценой меньшей видимости закрытия. Используйте её, когда существующая переменная находится прямо здесь; в противном случае предпочтите встроенное объявление.
Подавленные исключения
Вот тонкий момент. Что произойдёт, если тело try выбросит исключение и вызов close() тоже выбросит исключение? До Java 7 исключение из finally при закрытии заменяло исходное — и вы теряли настоящую причину.
try-with-resources решает эту проблему. Исходное исключение передаётся вызывающему коду; любые исключения, возникшие при закрытии, прикрепляются к нему как подавленные, и их можно получить через Throwable.getSuppressed():
try (Resource r = new Resource()) {
r.use(); // throws A
} // close() throws B
// caller sees A
// A.getSuppressed() returns [B]Стандартный printStackTrace() также выводит подавленные исключения (строки Suppressed: ... под основным). Вам почти никогда не придётся обрабатывать это вручную — язык сохраняет цепочку за вас.
Типичный реальный пример
Распространённый паттерн, объединяющий всё вместе:
public List<String> readAllLines(Path p) throws IOException {
try (BufferedReader reader = Files.newBufferedReader(p)) {
return reader.lines().toList();
}
}Обратите внимание: нет явного закрытия, нет finally, тело может напрямую использовать return, а вызывающий код всё равно получит чистое IOException, если что-то пошло не так.
Когда try-with-resources — не тот инструмент
Это правильный инструмент, когда вы владеете полным временем жизни ресурса в пределах одного блока. Он не подходит, когда:
- Ресурс живёт дольше блока — например, соединение, кешируемое между вызовами. Его закрытие в конце метода сломает следующего пользователя.
- Вы не владеете ресурсом — его передал вам вызывающий код. Он закроет его сам.
В таких случаях оставьте закрытие владельцу. Если вы не можете определить, кто является владельцем, это признак проблемы проектирования — разберитесь с этим до написания кода.
Проработанный пример
Короткая программа, которая копирует строку во внутрипроцессный канал и считывает её обратно через буферизованный ридер. Оба потока реализуют AutoCloseable; оба закрываются автоматически; мы добавляем небольшой пользовательский ресурс, чтобы вы могли увидеть порядок закрытия в выводе.
В выводе вы можете видеть, что порядок объявления — a, b, src, buf, но порядок закрытия обратный: buf, src, b, a. Это гарантия языка — первый открытый, последний закрытый.
Что дальше
До сих пор мы только перехватывали исключения, которые Java бросала на нас. Реальный код также должен генерировать собственные — для недопустимых аргументов, нарушенных инвариантов или доменных ошибок. Продолжайте изучение в разделе Java throw и throws.