Кто обязан закрыть ресурс, возвращённый фабричным методом, если ресурс не передан в try-with-resources?
Автоматически ресурс никто не закроет: его должен закрыть владелец, которому фабричный метод передал ресурс. Обычно ответственность переходит к вызывающему коду, поэтому он должен обернуть ресурс в try-with-resources; сборка мусора не является механизмом своевременного освобождения внешних ресурсов.
До появления try-with-resources разработчики вручную закрывали файлы, соединения и другие ресурсы в блоках finally. Это приводило к дублированию кода и ошибкам: ресурс могли не закрыть при исключении или закрытие могло нарушить обработку исходной ошибки.
В Java 7 появился стандартизированный механизм автоматического закрытия объектов AutoCloseable. Однако он работает только для ресурсов, явно переданных в область управления try-with-resources; сам факт возврата объекта из фабрики не создаёт для него автоматического владельца.
Фабрика может открыть соединение с базой данных и вернуть его вызывающему коду. После возврата фабрика больше не контролирует жизненный цикл объекта, поэтому закрыть его внутри фабрики нельзя: это сделало бы возвращённое соединение непригодным для использования.
Если вызывающий код не закроет ресурс, возможны утечки файловых дескрипторов, соединений, памяти нативной библиотеки или других ограниченных системных ресурсов. Garbage Collector освобождает память Java-объекта, но не гарантирует своевременное закрытие таких ресурсов.
Фабричный метод должен явно документировать передачу ответственности. Если он возвращает открытый ресурс, обычно его закрывает вызывающая сторона, поместив получение ресурса непосредственно в try-with-resources или передав уже полученный ресурс в такую конструкцию.
В примере фабричный метод только создаёт и возвращает ресурс. Метод firstLine становится его временным владельцем и гарантирует вызов close() после успешного открытия, независимо от результата чтения.
Если фабрика не возвращает ресурс, а сама выполняет всю операцию, ответственность остаётся внутри фабрики: она должна закрыть ресурс до завершения метода. Нельзя одновременно закрывать ресурс внутри фабрики и ожидать, что вызывающий код продолжит им пользоваться.
Практический компромисс состоит в выборе границы владения. Передача открытого ресурса даёт вызывающему коду гибкость, но требует чёткого контракта; передача только результата операции упрощает управление ресурсом, но ограничивает контроль вызывающей стороны.
Для сложных объектов полезно явно описывать в документации, кто создаёт ресурс, кто обязан вызвать close() и можно ли передавать ресурс между потоками. try-with-resources не решает проблему неясного владения — он лишь надёжно выполняет закрытие ресурса, который уже помещён в его область действия.
Сервис работы с базой данных предоставляет метод, возвращающий открытый Connection. Рассматривались три варианта.
close() может постепенно исчерпать пул соединений.Выбирается третий вариант, если API действительно должен передавать открытое соединение. Для ещё более безопасного дизайна сервис может не возвращать соединение вообще, а принимать функцию или выполнять готовую операцию внутри своей границы владения. Тогда число мест, где разработчик обязан управлять ресурсом, уменьшается.
Дополнительный вопрос 1: Достаточно ли положиться на финализатор или сборщик мусора, если вызывающий код забыл закрыть ресурс?
Нет. Сборщик мусора управляет памятью, а не сроком жизни файловых дескрипторов, сокетов или соединений. Даже если конкретный класс имеет механизм очистки, момент его выполнения не гарантирован, поэтому для ограниченных ресурсов нужен явный close(), обычно через try-with-resources.
Дополнительный вопрос 2: Кто закрывает ресурс, если метод получает уже открытый ресурс от вызывающего кода и только использует его?
Это определяется контрактом владения, а не самим типом AutoCloseable. Если метод лишь временно заимствует ресурс, закрывать его обычно должен внешний владелец; иначе метод может неожиданно сделать объект непригодным для дальнейшего использования. Если же контракт передаёт владение методу, тот обязан закрыть ресурс, в том числе при исключении.
Дополнительный вопрос 3: Почему фабрике иногда лучше возвращать не открытый ресурс, а результат операции?
Возврат результата позволяет фабрике полностью контролировать жизненный цикл ресурса: открыть его, выполнить работу и закрыть в одной области ответственности. Это снижает риск утечек и упрощает API, но лишает вызывающий код возможности выполнять несколько операций в рамках одного ресурса или управлять транзакцией. Такой вариант выбирают, когда гибкость открытого ресурса не нужна и безопасность владения важнее.