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 .