Беды ручного finally
До Java 7 ресурсы (файлы, соединения, потоки) закрывали в finally. Это многословно, требует вложенных try для нескольких ресурсов и легко ошибиться: если close() в finally сам бросит исключение, оно ЗАТРЁТ исходное исключение из тела try — и настоящая первопричина потеряется.
Плюс типичная ловушка: если ресурс не создался (исключение в инициализации), переменная остаётся null, и close() в finally даёт ещё и NullPointerException.
try-with-resources
try-with-resources автоматически закрывает всё, что реализует AutoCloseable, объявленное в круглых скобках try — гарантированно, даже при исключении или return в теле. Это убирает boilerplate и весь класс ошибок ручного закрытия.
Никогда не «глотайте» исключение пустым catch — это прячет ошибки и усложняет отладку.
try (var in = Files.newInputStream(path);
var out = Files.newOutputStream(dst)) {
in.transferTo(out); // оба ресурса закроются автоматически,
} catch (IOException e) { // out — раньше in (обратный порядок)
throw new UncheckedIOException(e);
}
Suppressed-исключения
try-with-resources решает проблему «затирания»: если исключение бросает И тело try, И close(), то основным остаётся исключение из тела, а исключение из close() не теряется — оно прикрепляется к основному как suppressed и доступно через Throwable.getSuppressed().
Это противоположно поведению ручного finally (где побеждало исключение из close). В stack trace такие добавки печатаются с пометкой «Suppressed:» — первопричина видна.
Throwable[] suppressed = primary.getSuppressed(); // что бросили close() при раскрутке
Несколько ресурсов и порядок
Ресурсы закрываются в порядке, ОБРАТНОМ объявлению (последний открытый закрывается первым) — это логично для зависимых ресурсов (Writer поверх OutputStream).
С Java 9 в try-with-resources можно перечислять уже существующие effectively final переменные, а не только объявлять новые на месте: try (resource) { ... }.
AutoCloseable против Closeable
AutoCloseable — базовый интерфейс с единственным методом close() throws Exception; его достаточно для участия в try-with-resources. Closeable (из java.io) — его наследник, у которого close() throws более узкое IOException и по контракту должен быть идемпотентным (повторный close безвреден).
Свой ресурс делают AutoCloseable; close() желательно делать идемпотентным и по возможности не бросающим — исключение из close усложняет жизнь вызывающим.
Как отвечать на собеседовании
Объясните, чем плох ручной finally (затирание исходного исключения, NPE на null-ресурсе), как try-with-resources закрывает AutoCloseable в обратном порядке, и главное — механизм suppressed-исключений (исключение из close прикрепляется, а не затирает; getSuppressed). Различите AutoCloseable (Exception, общий) и Closeable (IOException, идемпотентный).