Перейти к основному содержимому

Разница между map() и flatMap()

· 3 мин. чтения

1. Обзор

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

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

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

2. Карта и плоская карта в опциях

Метод map() хорошо работает с Optional — если функция возвращает именно тот тип, который нам нужен:

Optional<String> s = Optional.of("test");
assertEquals(Optional.of("TEST"), s.map(String::toUpperCase));

Однако в более сложных случаях нам может быть предоставлена функция, которая также возвращает необязательный параметр. В таких случаях использование map() привело бы к вложенной структуре, так как реализация map() выполняет дополнительную внутреннюю оболочку.

Давайте посмотрим на другой пример, чтобы лучше понять эту ситуацию:

assertEquals(Optional.of(Optional.of("STRING")), 
Optional
.of("string")
.map(s -> Optional.of("STRING")));

Как мы видим, мы получаем вложенную структуру Optional<Optional<String>> . Хотя это и работает, но довольно громоздко в использовании и не обеспечивает никакой дополнительной защиты от null, поэтому лучше сохранить плоскую структуру.

Это именно то, что нам помогает сделать flatMap() :

assertEquals(Optional.of("STRING"), Optional
.of("string")
.flatMap(s -> Optional.of("STRING")));

3. Карта и плоская карта в потоках

Оба метода работают одинаково для Optional .

Метод map() оборачивает базовую последовательность в экземпляр Stream , тогда как метод flatMap() позволяет избежать вложенной структуры Stream<Stream<R>> .

Здесь map() создает поток , состоящий из результатов применения метода toUpperCase() к элементам входного потока :

List<String> myList = Stream.of("a", "b")
.map(String::toUpperCase)
.collect(Collectors.toList());
assertEquals(asList("A", "B"), myList);

map() работает очень хорошо в таком простом случае. Но что, если у нас есть что-то более сложное, например, список списков в качестве входных данных?

Давайте посмотрим, как это работает:

List<List<String>> list = Arrays.asList(
Arrays.asList("a"),
Arrays.asList("b"));
System.out.println(list);

Этот фрагмент выводит список списков [[a], [b]] .

Теперь воспользуемся flatMap() :

System.out.println(list
.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList()));

Результат такого фрагмента будет сглажен до [a, b] .

Метод flatMap () сначала сводит входной поток потоков к потоку строк (подробнее о сведении см. в этой статье ) . После этого он работает аналогично методу map() . [](/lessons/b/-java-flatten-nested-collections)

4. Вывод

Java 8 дает нам возможность использовать методы map() и flatMap() , которые изначально использовались в функциональных языках.

Мы можем вызывать их в потоках и опционах. Эти методы помогают нам получать сопоставленные объекты, применяя предоставленную функцию сопоставления.

Как всегда, примеры из этой статьи доступны на GitHub .