В каком порядке выполняются несколько обработчиков, зарегистрированных у одного Stream через onClose?
Обработчики onClose выполняются в порядке их регистрации: сначала запускается ранее добавленный обработчик, затем последующие. Если один обработчик выбрасывает исключение, остальные всё равно запускаются, а дополнительные исключения добавляются к первому как подавленные.
Потоки Java могут быть связаны с внешними ресурсами: файлами, сетевыми соединениями или другими источниками данных. Поэтому Stream API предоставляет механизм закрытия, позволяющий подключить освобождение ресурса к жизненному циклу стрима.
Подход с обработчиками закрытия отделяет обработку элементов от управления ресурсами. Это делает конвейер выразительнее, но не отменяет необходимости фактически вызвать close — обычно через конструкцию try-with-resources.
Один и тот же стрим может получить несколько обработчиков закрытия в разных слоях программы. Например, библиотека может зарегистрировать освобождение внутреннего ресурса, а прикладной код — собственную очистку.
Если порядок выполнения не учитывать, очистка может произойти слишком рано или слишком поздно. Дополнительный риск возникает при исключении: ошибка одного обработчика не должна препятствовать освобождению остальных ресурсов.
Каждый вызов onClose добавляет обработчик к цепочке закрытия стрима. При вызове close эта цепочка выполняется последовательно, в порядке регистрации: первый добавленный обработчик запускается первым.
Обработчики не запускаются после терминальной операции автоматически. Терминальная операция исчерпывает стрим, но закрытие — отдельное действие. Для стрима, который владеет ресурсом, следует использовать try-with-resources.
Если обработчик выбрасывает исключение, механизм закрытия продолжает запускать остальные обработчики. Первое возникшее исключение становится основным, а последующие доступны через getSuppressed(). Поэтому обработчики очистки должны быть максимально надёжными и не скрывать исходную ошибку без необходимости.
Важно отличать обработчик onClose от финализатора и от побочного эффекта обработки элементов. Он связан именно с закрытием стрима и не гарантирует выполнение при аварийном завершении виртуальной машины или принудительном завершении процесса.
Сервис читает данные из файла через стрим. Один слой регистрирует закрытие файлового ресурса, а другой добавляет обработчик для метрик.
Первый вариант — закрывать ресурс вручную в нескольких местах. Его минус — высокий риск пропустить ветку с исключением или закрыть ресурс дважды. Плюс такого подхода только в простоте для очень короткого кода.
Второй вариант — зарегистрировать очистку через onClose, а сам стрим обернуть в try-with-resources. Это связывает освобождение ресурса с закрытием стрима, сохраняет порядок обработчиков и гарантирует вызов close при выходе из блока.
Выбран второй вариант: обработка данных остаётся независимой от очистки, обработчики выполняются предсказуемо, а исключения очистки не блокируют запуск последующих обработчиков. Результат — меньше утечек ресурсов и более диагностируемое поведение при ошибках.
Нет. Терминальная операция не вызывает close автоматически. Если стрим связан с ресурсом, его нужно явно закрыть; стандартный способ — try-with-resources.
Остальные зарегистрированные обработчики всё равно должны быть запущены. Первое исключение считается основным, а исключения из последующих обработчиков добавляются к нему как подавленные, поэтому их можно исследовать через getSuppressed().
Только если закрытие стрима действительно вызывается и обработчик зарегистрирован корректно. onClose не является универсальным сборщиком мусора и не срабатывает автоматически при потере ссылки на стрим. Кроме того, обработчик должен освобождать ресурс, которым фактически владеет соответствующий код; иначе легко получить двойное закрытие или нарушение ответственности между слоями.