Ресурсы и try-with-resources
IO-объекты часто владеют системными ресурсами
Файловый поток — это не просто обычный объект в heap. За ним может стоять файловый дескриптор, сокет, канал или другой ресурс операционной системы. Сборщик мусора управляет памятью, но не является механизмом, на который следует полагаться для своевременного освобождения таких ресурсов.
Большинство IO-классов реализуют Closeable, который является разновидностью
AutoCloseable. Поэтому их естественный жизненный цикл выражается через уже знакомый
try-with-resources.
Path path = Path.of("data.txt");
try (InputStream in = Files.newInputStream(path)) {
byte[] data = in.readNBytes(32);
System.out.println(data.length);
}
Когда управление покидает блок — нормально или из-за исключения — Java вызывает close() автоматически.
Это делает закрытие частью структуры программы, а не отдельным действием, которое легко забыть.
Если ресурсов несколько
Ресурсы объявляются слева направо, а закрываются в обратном порядке. Это удобно для цепочек обёрток: сначала закрывается внешний объект, затем тот, на котором он построен.
try (FileInputStream file = new FileInputStream("data.bin");
BufferedInputStream buffered = new BufferedInputStream(file)) {
System.out.println(buffered.read());
}
Здесь сначала закроется buffered, затем file.
На практике, если внешний поток явно владеет внутренним, часто достаточно хранить как ресурс только внешний объект,
потому что его close() закрывает цепочку дальше.
Основное исключение и suppressed
Если ошибка возникла внутри блока и ещё одна ошибка произошла при close(),
Java не теряет первоначальную причину. Исключение из тела остаётся основным,
а исключение закрытия прикрепляется к нему как suppressed и доступно через getSuppressed().
Что считать ресурсом
try-with-resources нужен не только файлам.
Им можно управлять любым объектом, реализующим AutoCloseable:
потоками, reader/writer, каналами и многими объектами других API.
Главный вопрос один: есть ли у объекта внешний ресурс или состояние, которое должно быть освобождено предсказуемо.