Что делает операция filter()?
Принимает Predicate
🟢 Junior Level
filter(Predicate) — это промежуточная операция, которая оставляет только те элементы, которые удовлетворяют условию.
Почему лучше if внутри цикла: filter можно комбинировать в цепочки, менять порядок операций и легко параллелить без изменения логики. if внутри цикла — жёстко зашит в императивный стиль.
Принимает Predicate<T> — функциональный интерфейс с методом boolean test(T t).
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Оставляем только чётные числа
List<Integer> even = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
// Результат: [2, 4, 6, 8, 10]
// Фильтрация null-элементов
list.stream()
.filter(Objects::nonNull)
.forEach(System.out::println);
Важно: filter сам по себе не запускает обработку — нужна terminal операция.
🟡 Middle Level
Внутренняя реализация
filter создает Sink.ChainedReference, метод accept которого:
Sink.ChainedReference — внутренний класс, который оборачивает следующий Sink в цепочке и вызывает его accept() только если Predicate возвращает true.
public void accept(T t) {
if (predicate.test(t)) {
downstream.accept(t); // передаём дальше
}
// иначе — элемент "проглатывается"
}
Stateless природа
Фильтр не зависит от других элементов — каждый обрабатывается независимо. Это делает filter идеальным для parallelStream.
Ранняя фильтрация (Early Filtering)
Чем раньше отсечете лишние данные, тем меньше работы у последующих дорогих операций:
// ХОРОШО — фильтр в начале
stream.filter(Objects::nonNull)
.filter(s -> s.startsWith("A"))
.map(expensiveMapping)
// ПЛОХО — expensiveMapping вызовется для всех элементов
stream.map(expensiveMapping)
.filter(Objects::nonNull)
Цепочки фильтров vs сложный предикат
// Вариант А — читаемее
stream.filter(Objects::nonNull)
.filter(s -> s.startsWith("A"))
// Вариант Б — чуть быстрее
stream.filter(s -> s != null && s.startsWith("A"))
Для большинства случаев приоритетнее читаемость (Вариант А).
🔴 Senior Level
Predicate Complexity и производительность
Если предикат выполняет тяжелую логику (регулярки, вызов внешнего кеша), он будет вызван для каждого элемента. Кэшируйте результаты тяжелых вычислений.
Short-circuiting взаимодействие
filter работает в паре с операциями короткого замыкания (findFirst, limit). Если фильтр нашел подходящий элемент, конвейер остановится — остальные элементы не обрабатываются.
Null-checks как стандарт де-факто
filter(Objects::nonNull) — стандартный паттерн для очистки данных перед обработкой, предотвращает NPE в последующих map.
Edge Cases
- Side Effects в предикате: Никогда не меняйте внешние переменные внутри
filter— это нарушает функциональную парадигму и приведет к багам в параллельных стримах - Optional.filter: У класса
Optionalтоже естьfilter(), работает аналогично — превращает заполненный Optional в пустой при несовпадении условия
Диагностика
Filtering Metrics: Если фильтр отсеивает 99.9% данных в Java-коде — перенесите фильтрацию на уровень SQL-запроса.
Logging с peek():
stream.peek(e -> log.debug("Before: " + e))
.filter(predicate)
.peek(e -> log.debug("After: " + e))
🎯 Шпаргалка для интервью
Обязательно знать:
filter(Predicate)— промежуточная операция, оставляет элементы, удовлетворяющие условию- Stateless — каждый элемент обрабатывается независимо, идеально для parallelStream
- Ранняя фильтрация: чем раньше filter, тем меньше работы у последующих дорогих операций
- Работает с short-circuit операциями (findFirst, limit) — конвейер останавливается при нахождении
filter(Objects::nonNull)— стандартный паттерн для защиты от NPE- Фильтр сам не запускает обработку — нужна terminal операция
Частые уточняющие вопросы:
- filter vs if внутри цикла? — filter декларативен, легко комбинировать и параллелить
- Цепочка фильтров или один сложный предикат? — Цепочка читаемее, один предикат чуть быстрее
- Что если предикат тяжёлый? — Кэшируйте результаты, вызывается для каждого элемента
- Можно ли менять внешние переменные в предикате? — Нет, это нарушает функциональную парадигму
Красные флаги (НЕ говорить):
- «filter сразу обрабатывает все элементы» — нет, ленивый до terminal операции
- «Side effects в предикате — это нормально» — нет, приводит к багам в параллельных стримах
- «filter сортирует элементы» — нет, только отфильтровывает, порядок сохраняется
- «Если фильтр отсеивает 99% — это ок в Java-коде» — лучше перенести на уровень SQL
Связанные темы:
- [[2. В чём разница между intermediate и terminal операциями]]
- [[4. Что делает операция map()]]
- [[9. Что такое параллельные стримы]]
- [[5. Что делает операция collect()]]