В цепочке фильтрации один из предикатов объединён через Predicate.and: при каком результате первого предика...

В цепочке фильтрации один из предикатов объединён через Predicate.and: при каком результате первого предиката второй гарантированно не запускается?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Если первый предикат возвращает false, второй предикат не вызывается. Predicate.and использует ленивую проверку с коротким замыканием: второй предикат вычисляется только после успешного прохождения первого.

Исторический контекст

Функциональные интерфейсы появились в Java 8 вместе с лямбда-выражениями и Stream API. Для Predicate были добавлены методы композиции, включая and, or и negate, чтобы строить условия без ручного написания вспомогательных классов и вложенных if.

Такой подход решает две задачи: позволяет переиспользовать проверки и сохраняет логическую оптимизацию короткого замыкания, знакомую по операторам && и ||.

Постановка проблемы

Предикаты могут выполнять дорогие операции: обращаться к кэшу, разбирать данные или вычислять сложное условие. Если дорогую проверку запускать для каждого элемента независимо от результата дешёвой предварительной проверки, возрастут время обработки и количество побочных обращений.

Неверный порядок особенно опасен, когда второй предикат зависит от предположения, проверяемого первым. Например, первая проверка может подтверждать, что строка непустая, а вторая — безопасно обращаться к её содержимому.

Подробное решение

Вызов first.and(second) логически эквивалентен последовательности: сначала выполняется first, затем second только при результате true. Поэтому при false итог сразу становится false, а второй предикат не получает текущий элемент.

import java.util.List; import java.util.function.Predicate; import java.util.stream.Stream; Predicate<String> nonEmpty = s -> !s.isEmpty(); Predicate<String> startsWithA = s -> { System.out.println("проверка второго"); return s.startsWith("А"); }; List<String> result = Stream.of("", "Анна", "Борис") .filter(nonEmpty.and(startsWithA)) .toList();

Для пустой строки startsWithA не запускается. Для остальных строк сначала выполняется nonEmpty, затем — startsWithA; результатом будут строки, начинающиеся с «А».

Порядок важен: and не переставляет предикаты автоматически. Обычно первым ставят дешёвое и достаточно селективное условие, а дорогую проверку — вторым. Если предикаты имеют побочные эффекты или могут выбрасывать исключения, перестановка меняет наблюдаемое поведение, поэтому составные предикаты предпочтительно делать чистыми.

Вызов and с null приводит к NullPointerException при построении составного предиката. Если первый предикат выбросил исключение, второй также не выполняется. В параллельном стриме это правило действует для каждого элемента отдельно, но порядок обработки разных элементов между потоками не гарантируется.

Ситуация из практики

Сервис фильтрует большой поток объектов: сначала нужно проверить локальный статус объекта, а затем выполнить дорогую проверку доступности связанного ресурса.

Вариант с независимым запуском обеих проверок тратит ресурсы даже для объектов с неподходящим статусом. Вариант с двумя последовательными операциями filter также корректен и часто лучше читается; его преимущество — простая диагностика каждого этапа, а недостаток — более раздельное описание общего условия.

Выбран составной предикат с дешёвой проверкой статуса первым, если условие используется как единая переиспользуемая политика. Это сохраняет короткое замыкание и уменьшает число обращений к ресурсу. Если проверки требуют разных метрик или трассировки, предпочтительнее две операции filter, хотя механизм невычисления последующего условия останется тем же.

Что кандидаты часто упускают

  1. Сохраняется ли короткое замыкание у and в параллельном стриме?

Да, но только внутри проверки одного элемента: второй предикат не вызывается, если первый вернул false. Параллельность не отменяет этот контракт. При этом разные элементы могут проверяться одновременно, поэтому нельзя делать вывод о глобальном порядке вызовов или использовать небезопасное общее состояние.

  1. Можно ли переставлять предикаты в and без изменения результата?

Только если предикаты чистые, не выбрасывают зависящие от порядка исключения и не имеют наблюдаемых побочных эффектов. В математическом смысле логическое AND коммутативно, но программные функции могут менять состояние, обращаться к внешним системам или зависеть от времени. Тогда перестановка способна изменить не только производительность, но и итоговое поведение.

  1. Чем отличается Predicate.and от последовательного применения двух фильтров?

Для обычных чистых предикатов оба варианта логически дают одинаковый набор элементов и сохраняют короткое замыкание: второй фильтр получает элемент только после успешного первого. Составной предикат удобен как единый объект, который можно передать или переиспользовать. Два фильтра обычно лучше показывают этапы конвейера и упрощают отдельное профилирование, но не создают принципиально другой гарантии вычисления второго условия.