Optional.flatMap и Optional.map в Java: отличия с примерами
Быстрый ответ
Метод Optional.map позволяет преобразовать текущее значение с помощью переданной функции, после чего оборачивает получившийся результат в Optional.
Метод Optional.flatMap также преобразует значение, однако функция, которую он ожидает в качестве аргумента, должна возвращать Optional. Это позволяет избежать создания вложенных Optional.
Рассмотрим пример с map и функцией, не возвращающей Optional:
Optional<String> opt = Optional.of("test");
Optional<Integer> length = opt.map(String::length); // Optional[4]
Пример с flatMap и функцией, возвращающей Optional:
Optional<String> opt = Optional.of("test");
Optional<Integer> length = opt.flatMap(s -> Optional.of(s.length())); // Optional[4]
В случае с flatMap не происходит получения Optional<Optional<Integer>>, вместо этого мы получаем Optional<Integer>.

Выбор правильного метода
map для простой трансформации
Используйте map, чтобы применить функцию к значению в Optional и возвратить результат в новой Optional обертке:
Optional<String> text = Optional.of("1024");
Optional<Integer> number = text.map(Integer::valueOf); // Optional[1024]
flatMap для устранения вложенности
Если передаваемая в map функция возвращает Optional, то её следует заменить на flatMap для избежания создания вложенной структуры, что упростит код:
Optional<String> text = Optional.of("optional");
Optional<Optional<Integer>> badExample = text.map(s -> Optional.of(s.length())); // Неудачно: Optional[Optional[8]]
Optional<Integer> goodExample = text.flatMap(s -> Optional.of(s.length())); // Удачно: Optional[8]
flatMap эффективно сочетает в себе преобразование значения и устранение вложенности.
Выбирайте метод в зависимости от возвращаемого типа
Выбор между map и flatMap зависит от того, возвращает ли передаваемая функция Optional.
Избегайте NullPointerException
Перед вызовом get() на Optional, рекомендуется проверять наличие значения с помощью isPresent():
optionalValue.ifPresent(val -> System.out.println("Value: " + val));
Ответственное обращение с исключениями в методах
При использовании методов map и flatMap предусматривайте обработку возможных исключений, чтобы гарантировать корректное выполнение кода:
Optional<String> text = Optional.of("42");
Optional<Integer> number = text.flatMap(s -> {
    try {
        return Optional.of(Integer.parseInt(s));
    } catch (NumberFormatException e) {
        return Optional.empty();
    }
}); // Optional[42]
Визуализация
Предположим, нам нужно доставить другу подарок:
Optional.map:    [🎁] -> 🏠👉🏠 (Подарок оставлен у входа)
Optional.flatMap: [🎁] -> 🚛 -> 🏠 (Подарок доставлен аж до рук получателя)
Вывод: map прямо трансформирует содержащееся значение, тогда как flatMap позволяет контролировать процесс обработки данных.
Адаптация к различным сценариям
Последовательные операции
Для последовательного преобразования данных, в процессе которого каждый шаг может возвращать Optional, используйте цепочку flatMap:
Optional<String> result = optionalUser
    .flatMap(User::getAddress)
    .flatMap(Address::getCountry)
    .map(Country::getName);
Преобразование в Streams
Optional можно преобразовать в Stream для выполнения последующих операций:
Stream<String> stream = optionalString
    .map(Stream::of)
    .orElseGet(Stream::empty);
Теоретические основы в Java 8
Для более глубокого изучения теоретических основ обратите внимание на книгу "Java 8 In Action", где подробно описаны функциональные возможности Java 8.
Полезные материалы
- Optional (Java Platform SE 8 ) — официальная документация Java по Optional.flatMap.
- Optional (Java Platform SE 8 ) — руководство Oracle по Optional.map.
- Understanding map and flatMap — статья, разъясняющая различия и сходства между mapиflatMap.
- Baeldung on Java Optional — руководство Baeldung по использованию Optional.
- DZone — обсуждение на DZone, ещё один взгляд на разницу между map()иflatMap().
- Обсуждение map против flatMap — ветка на Stack Overflow, посвящённая сравнению Optional.flatMapиOptional.map.


