Статья

Различия между map() и flatMap() в Java

API map() и flatMap() – это основа функциональных языков. В Java 8 мы можем найти map и flatmap в классах Optional, Stream и в CompletableFuture (хотя и под немного другим названием).

Стримы (streams) представляют собой последовательность объектов, Optional – это классы, которые представляют значение, которое может присутствовать или отсутствовать. Среди агрегатных операций есть методы map() и flatMap().

Несмотря на то, что оба метода имеют одинаковые типы возвращаемых данных, они совершенно разные. Давайте объясним эти различия, проанализировав некоторые примеры стримов и опционалов.

Map и Flatmap в опционалах

Метод map() хорошо работает с опционалами — если функция возвращает именно тот тип, который нам нужен:
Optional<String> s = Optional.of("Java");
assertEquals(Optional.of("JAVA"), s.map(String::toUpperCase));
Однако в более сложных случаях может использоваться функция, которая возвращает Optional параметр. В таких случаях использование map() привело бы к созданию вложенной структуры, поскольку реализация map() делает дополнительную внутреннюю обертку.

Давайте посмотрим на другой пример, чтобы лучше понять это:
assertEquals(Optional.of(Optional.of("JAVA")), 
  Optional
  .of("java")
  .map(s -> Optional.of("JAVA")));
Как видим, в итоге мы получаем вложенную структуру Optional<Optional<String>>. Это работает, но, довольно громоздко в использовании и не обеспечивает никакой null-безопасности , поэтому лучше использовать плоскую структуру.

flatMap() помогает нам это сделать:
assertEquals(Optional.of("JAVA"), Optional
  .of("java")
  .flatMap(s -> Optional.of("JAVA")));

Map и Flatmap в стримах

Оба метода работают аналогично для опционалов.
Метод map() оборачивает базовую последовательность в экземпляре Stream, тогда как метод flatMap() позволяет избежать вложенной структуры Stream<Stream<R>>.
Здесь map() создает стрим, состоящий из результатов применения метода toUpperCase() к элементам входного потока:
List<String> myList = Stream.of("hello", "world")
  .map(String::toUpperCase)
  .collect(Collectors.toList());
assertEquals(asList("HELLO", "WORLD"), myList);
map() работает довольно хорошо в этом простом случае. Но что, если у нас есть что-то более сложное, например, список списков в качестве входных данных?

Давайте посмотрим, как это работает:
List<List<String>> list = Arrays.asList(
  Arrays.asList("hello"),
  Arrays.asList("world"));
System.out.println(list);
Этот фрагмент кода выводит список списков [[hello], [world]].

Теперь давайте используем flatMap():
System.out.println(list
  .stream()
  .flatMap(Collection::stream)
  .collect(Collectors.toList()));
Результат такого фрагмента будет сведен к [hello, world].
Метод flatMap() сначала сворачивает входной поток потоков до потока строк. После этого он работает аналогично методу map().

Заключение

Java 8 дает нам возможность использовать методы map() и flatMap(), которые изначально использовались в функциональных языках.
Мы можем вызывать их в стримах и опционалах. Эти методы помогают нам получать mapped объекты, применяя переданную map-функцию.
java