Питання 29 · Розділ 8

Як працювати з Optional в Stream?

Optional і Stream часто працюють разом. Основні сценарії:

Мовні версії: English Russian Ukrainian

🟢 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

  1. Проста перевірка на null — звичайний if (x != null) читабельніший
  2. Всередині tight loop — створення Optional = алокація, може навантажити GC
  3. Як поле класу — 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+, елегантний спосіб прибрати порожні Optional
  • orElse() обчислюється ЗАВЖДИ, 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]]