The Difference Between map() and flatMap()
Omar Ismail
Senior Software Engineer @ Digitinary | Java & Spring Expert ? | AWS & Microservices Architect ? | FinTech & Open Banking Innovator ?? | Digital Payments Expert ?? | Top 200 IT Content Creator in Jordan ?? | 40K+ ??
1. Overview
map()?and?flatMap()?APIs stem from functional languages. In Java 8, we can find them in?Optional,?Stream?and in?CompletableFuture?(although under a slightly different name).
Streams?represent a sequence of objects whereas optionals are classes that represent a value that can be present or absent. Among other aggregate operations, we have the?map()?and?flatMap()?methods.
Despite the fact that both have the same return types, they are quite different. Let's explain these differences by analyzing some examples of streams and optionals.
2. Map and Flatmap in Optionals
The?map()?method works well with?Optional?— if the function returns the exact type we need:
However, in more complex cases we might be given a function that returns an?Optional?too. In such cases using?map()?would lead to a nested structure, as the?map()?implementation does an additional wrapping internally.
Let's see another example to get a better understanding of this situation:
assertEquals(Optional.of(Optional.of("STRING")),
Optional
.of("string")
.map(s -> Optional.of("STRING")));
As we can see, we end up with the nested structure?Optional<Optional<String>>. Although it works, it's pretty cumbersome to use and does not provide any additional null safety, so it is better to keep a flat structure.
That's exactly what?flatMap()?helps us to do:
assertEquals(Optional.of("STRING"), Optional
.of("string")
.flatMap(s -> Optional.of("STRING")));
3. Map and Flatmap in Streams
Both methods work similarly for?Optional.
The?map()?method wraps the underlying sequence in a?Stream?instance, whereas the?flatMap()?method allows avoiding nested?Stream<Stream<R>>?structure.
Here,?map()?produces a?Stream?consisting of the results of applying the?toUpperCase()?method to the elements of the input?Stream:
List<String> myList = Stream.of("a", "b")
.map(String::toUpperCase)
.collect(Collectors.toList());
assertEquals(asList("A", "B"), myList);
map()?works pretty well in such a simple case. But what if we have something more complex, such as a list of lists as an input?
Let's see how it works:
List<List<String>> list = Arrays.asList(
Arrays.asList("a"),
Arrays.asList("b"));
System.out.println(list);
This snippet prints a list of lists?[[a], [b]].
Now let's use a?flatMap():
System.out.println(list
.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList()));
The result of such a snippet will be flattened to?[a, b].
The?flatMap()?method first flattens the input?Stream?of?Streams?to a?Stream?of?Strings. Thereafter, it works similarly to the?map()?method.