Як працювати з Optional в Stream?
Optional і Stream часто працюють разом. Основні сценарії:
🟢 Junior Level
Optional і Stream часто працюють разом. Основні сценарії:
Optional.empty() — «контейнер без значення». Аналог null, але безпечний: не можна випадково отримати NPE, потрібно явно викликати get/orElse.
1. Стрим містить Optional:
// Було: Stream<Optional<User>>
// Потрібно: Stream<User> (без порожніх)
ids.stream()
.map(repo::findById) // повертає Optional<User>
.filter(Optional::isPresent)
.map(Optional::get)
.collect(toList());
2. Повернути Optional із stream-операцій:
Optional<User> firstAdmin = users.stream()
.filter(User::isAdmin)
.findFirst(); // повертає Optional
Важливо: Ніколи не викликайте .get() без перевірки .isPresent().
🟡 Middle Level
Java 9+ стиль: Optional.stream()
Елегантний спосіб позбутися порожніх Optional:
List<User> activeUsers = ids.stream()
.map(repository::findById) // Stream<Optional<User>>
.flatMap(Optional::stream) // Порожні Optional зникають!
.toList();
Under the hood: Optional.stream() повертає Stream.of(value) або Stream.empty().
Уникайте .get() у лямбдах
// ПОГАНО — міна уповільненої дії
.map(o -> o.get())
// ДОБРЕ — безпечно
.flatMap(Optional::stream)
.map(o -> o.orElse(defaultValue))
orElse() — обчислюється ЗАВЖДИ, навіть якщо Optional не порожній. orElseGet() — обчислюється ТІЛЬКИ якщо Optional порожній. Якщо дефолтне значення дороге (DB query) — завжди orElseGet!
Просунуті методи (Java 9-11)
ifPresentOrElse(action, emptyAction):
optional.ifPresentOrElse(
user -> sendNotification(user),
() -> log.warn("User not found")
);
or(Supplier):
// Ланцюжок пошуку: кеш → БД → API
findByIdInCache(id)
.or(() -> findByIdInDb(id))
.or(() -> findByIdInApi(id));
Коли НЕ використовувати Optional в Stream
- Проста перевірка на null — звичайний
if (x != null)читабельніший - Всередині tight loop — створення Optional = алокація, може навантажити GC
- Як поле класу — Optional не серіалізується, не для полів
🔴 Senior Level
Не використовуйте Optional для полів Entity
Зберігання Optional у полях класів (особливо JPA) — антипатерн:
Optionalне серіалізується- Збільшує споживання пам’яті
- Використовуйте тільки як повертаєме значення методів
Highload: Optional Overhead
Створення мільйонів Optional може бути накладним:
- Value Types (Project Valhalla): У майбутньому
Optionalстане “Inline Type” без overhead - Сьогодні: Для мільярдів примітивів використовуйте “магічні значення” (
-1,0) абоOptionalInt
Edge Cases
NullPointerException: Optional.of(null) кидає NPE негайно. Використовуйте Optional.ofNullable().
Parallel Streams: Optional у паралельних стримах безпечний — Optional іммутабельний.
Діагностика
Sonar Rule: “Optional should be used as a return type only”. Ігноруйте тільки у виняткових випадках.
Debugging: При налагодженні flatMap(Optional::stream) порожні значення просто зникають із візуалізатора — це може збити з пантелику.
🎯 Шпаргалка для інтерв’ю
Обов’язково знати:
filter(Optional::isPresent).map(Optional::get)— старий стиль Java 8 для розпакуванняflatMap(Optional::stream)— Java 9+, елегантний спосіб прибрати порожні OptionalorElse()обчислюється ЗАВЖДИ,orElseGet()— тільки якщо Optional порожній (для дорогих дефолтів)Optional.of(null)кидає NPE — використовуйтеOptional.ofNullable()- Optional — тільки як повертаєме значення методів, НЕ як поле класу
- Optional не серіалізується і не для полів JPA-Entity
ifPresentOrElse(action, emptyAction)таor(Supplier)— Java 9+ для просунутої обробки- У tight loop створення мільйонів Optional тисне на GC — розгляньте OptionalInt/Long
Часті уточнюючі запитання:
- Чим orElse відрізняється від orElseGet? — orElse обчислює аргумент завжди; orElseGet — тільки при порожньому Optional. Для дорогих дефолтів (DB query) — завжди orElseGet.
- **Як перетворити Stream<Optional
> на Stream ?** — `.flatMap(Optional::stream)` (Java 9+) — порожні Optional зникнуть. - Чому Optional не варто використовувати як поле класу? — Не серіалізується, збільшує пам’ять, ускладнює код — призначений тільки для повертаємів значень.
- Чи безпечний Optional у parallelStream? — Так, Optional іммутабельний, але overhead на створення об’єктів залишається.
Червоні прапорці (НЕ говорити):
- «Можна викликати .get() без перевірки» — неправда, порожній Optional кине NoSuchElementException
- «Optional — заміна null для полів класу» — неправда, це антипатерн, Optional тільки для return type
- «orElse та orElseGet однакові» — неправда, orElse обчислюється завжди, orElseGet — ледачо
- «Optional.of(null) поверне порожній Optional» — неправда, кине NullPointerException
Пов’язані теми:
- [[26. Що роблять операції findFirst() та findAny()]]
- [[25. Що таке операції anyMatch(), allMatch(), noneMatch()]]
- [[22. Коли починається виконання операцій в Stream]]
- [[27. Як зібрати Stream в Map]]