Вопрос 29 · Раздел 8

Как работать с Optional в Stream?

Optional и Stream часто работают вместе. Основные сценарии:

Версии по языкам: English Russian Ukrainian

🟢 Junior Level

Optional и Stream часто работают вместе. Основные сценарии:

Optional.empty() — «контейнер без значения». Аналог null, но безопасный: нельзя случайно получить NPE, нужно явно вызвать get/orElse.

1. Stream содержит 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 не serializable, не для полей

🔴 Senior Level

Не используйте Optional для полей Entity

Хранение Optional в полях классов (особенно JPA) — антипаттерн:

  • Optional не сериализуем
  • Увеличивает потребление памяти
  • Используйте только как возвращаемое значение методов

Highload: Optional Overhead

Создание миллионов Optional может быть накладным:

  • Value Types (Project Valhalla): В будущем Optional станет “Inline Type” без оверхеда
  • Сегодня: Для миллиардов примитивов используйте “магические значения” (-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 иммутабелен, но оверхед на создание объектов остаётся.

Красные флаги (НЕ говорить):

  • «Можно вызывать .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]]