Что ограничивает использование уже созданной переменной в try-with-resources?
Уже созданную переменную можно указать в try-with-resources, только если она имеет тип, реализующий AutoCloseable, и является final или effectively final. Это гарантирует, что ссылка на ресурс не изменится между её созданием и автоматическим закрытием.
Try-with-resources появился в Java 7, чтобы заменить ручное закрытие ресурсов в блоке finally. Исходная проблема заключалась в том, что при нескольких путях выхода легко было забыть закрыть ресурс или скрыть исходное исключение ошибкой закрытия.
Изначально ресурс обычно объявляли непосредственно в заголовке try. Начиная с Java 9, язык разрешает использовать там уже существующую переменную, но сохранил требование неизменяемости ссылки.
Если ссылку на ресурс можно переназначить до завершения try, неочевидно, какой именно объект должен закрыть компилятор: первоначальный или объект, записанный в переменную позже. Автоматическое управление ресурсом должно иметь однозначный жизненный цикл.
Поэтому Java запрещает изменяемые локальные переменные в качестве ресурсов. В противном случае код либо не скомпилируется, либо потребовалось бы определять неоднозначные правила закрытия разных объектов.
Переменная считается effectively final, если ей присвоили значение один раз и после этого не переназначали. Явно указывать модификатор final необязательно.
В примере reader подходит как ресурс: BufferedReader реализует Closeable, а Closeable наследует AutoCloseable. После выхода из try компилятор обеспечит вызов close() для этого объекта.
Если после создания написать reader = другойReader, переменная перестанет быть effectively final, и конструкция не скомпилируется. При этом неизменяемой должна быть именно ссылка; состояние самого объекта менять разрешено.
Важно отличать переменную от объекта. Методы ресурса могут изменять его внутреннее состояние, но переназначение ссылки запрещено. Также ресурс должен быть доступен в точке объявления try; переменная, уже закрытая вручную, формально всё равно может быть указана, однако повторное закрытие зависит от контракта конкретного ресурса.
Ограничение относится к локальным переменным, используемым как ресурс. Оно не означает, что любой объект с методом close() автоматически подходит: тип должен реализовывать AutoCloseable либо его наследника.
Сервис получает InputStream, затем хочет заменить его на поток из резервного источника, если основной источник недоступен. Разработчик объявляет одну переменную и переназначает её, после чего пытается передать её в try-with-resources.
Вариант с переназначением удобен визуально, но не компилируется: переменная не является effectively final. Ручное закрытие в finally устраняет ограничение, но возвращает риск ошибок при нескольких ресурсах и исключениях.
Лучшее решение — создать отдельные неизменяемые переменные для каждого выбранного ресурса и передать в try только окончательно выбранный объект. Если выбор требует сложной логики, её выполняют до try, а сам ресурс сохраняют в отдельную ссылку без последующих присваиваний. Это делает владельца ресурса однозначным и сохраняет автоматическое закрытие.
Обязательно ли явно писать final у переменной-ресурса?
Нет. После Java 9 достаточно, чтобы переменная была effectively final: ей присвоили значение один раз и не переназначали. Явный final нужен только для документирования намерения или дополнительной защиты от будущего изменения кода.
Можно ли менять объект, если ссылка на него effectively final?
Да. Ограничивается только ссылка, а не внутреннее состояние объекта. Например, можно читать данные из BufferedReader или изменять содержимое изменяемого буфера, но нельзя присвоить переменной другой ресурс.
Почему тип с методом close() не всегда можно использовать в try-with-resources?
Компилятор проверяет не наличие метода с подходящим именем, а соответствие типу AutoCloseable. Класс должен реализовывать этот интерфейс напрямую или через наследование; иначе Java не рассматривает его экземпляры как управляемые ресурсы. Если изменить класс нельзя, обычно применяют адаптер, который реализует AutoCloseable и делегирует закрытие исходному объекту.