Що робить операція 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()]]