The Difference Between map() and flatMap() Methods in Java
In this article, we are going to see the difference between map and flatMap methods in Java.
1. Introduction
Java has provided Stream interface since version 8. The map()
and flatMap()
are two intermediate operations. Here are the method signatures:
/* Returns a stream consisting of the results of applying the given function to the elements of this stream. Type Parameters: R - The element type of the new stream Parameters: mapper - a non-interfering, stateless function to apply to each element */ <R> Stream<R> map(Function<? super T, ? extends R> mapper) /* Returns a stream consisting of the results of replacing each element of this stream with the contents of a mapped stream produced by applying the provided mapping function to each element. Each mapped stream is closed after its contents have been placed into this stream. (If a mapped stream is null an empty stream is used, instead.) */ <R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
Both map()
and flatMap()
accept a Function
interface and return a Stream
of objects. The difference is that map()
transforms into an Object, but the flatMap() transforms into a Stream
.
In this example, I will demonstrate:
- How to use
map()
to transform aString
, POJO, andList
to another object in 1-to-1 mapping. - How to use
flatMap()
to transform aString
, POJO, andList
to anotherStream
of objects.
2. Technologies Used
The example code in this article was built and run using:
- Java 11
- Maven 3.3.9
- Eclipse Oxygen
- Junit 4.12
3. Maven Project
3.1 Dependencies
I will include Junit
in the pom.xml
.
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>jcg.zheng.demo</groupId> <artifactId>java-map-flatmap-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <build> <sourceDirectory>src</sourceDirectory> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> <configuration> <release>11</release> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies> </project>
3.2 POJO
I will create a POJO
which will be used to transform an object.
POJO.java
package jcg.zheng.demo; public class POJO { private int id; private String name; public POJO(int id, String name) { super(); this.name = name; this.id = id; } public int getId() { return id; } public String getName() { return name; } public int nameWordCount() { return name.length(); } public void setId(int id) { this.id = id; } public void setName(String name) { this.name = name; } }
4. JUnit Test
4.1 Common Data
I will create a CommonData
which includes several list of String
, 2D array, and two lists of POJO
. These constants will be used by all three test classes.
CommonData.java
package jcg.zheng.demo; import java.util.Arrays; import java.util.List; public class CommonData { protected static final List<String> stringList1 = Arrays.asList("A", "B", "C"); protected static final List<String> stringList2 = Arrays.asList("x", "y", "z"); protected static final List<String> lowerCaseStrings = Arrays.asList("mary", "something", "end"); protected static final List<List<String>> listOfStringLists = Arrays.asList(stringList2, stringList1); protected static final String[][] string2DArray = new String[][] { { "apple", "pear" }, { "rice", "flour" }, { "pork", "beef" } }; protected static final List<POJO> listOfObjects = Arrays.asList(new POJO(10, "Mary"), new POJO(20, "Zheng"), new POJO(30, "Tom"), new POJO(40, "Johnson")); protected static final List<POJO> listOfObjectWithNullNames = Arrays.asList(new POJO(10, null), new POJO(20, "Zheng"), new POJO(30, "Tom"), new POJO(40, "Johnson")); }
4.2 MapTest
In this step, I will create a MapTest
class which has six test methods.
testMaptoInt
– converts aPOJO
object toLong
with thevalue of its name length.
testMapWithListString - transforms two List of Strings.
testMapWithPOJO - transforms a POJO to its name count
testMapWithPOJO_Exception - encounters a NullPointerException during the map operation.
testMapWithPOJO_handleRuntimeException - makes sure the mapper does not throw any RuntimeException based on
logic.
MapTest.java
package jcg.zheng.demo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.util.List; import java.util.function.Function; import java.util.function.ToIntFunction; import java.util.stream.Collectors; import java.util.stream.Stream; import org.junit.Test; import jcg.zheng.demo.POJO; public class MapTest extends CommonData { private Integer countNameLength(POJO pojo) { if (pojo != null && pojo.getName() != null) { return pojo.getName().length(); } return Integer.valueOf(0); } @Test public void testMaptoInt() { ToIntFunction<POJO> intMapper = POJO::nameWordCount; int[] intArray = listOfObjects.stream().mapToInt(intMapper).toArray(); assertEquals(listOfObjects.size(), intArray.length); } @Test public void testMapWithListString() { List<Integer> listSizes = Stream.of(stringList2, stringList1).map(List::size).collect(Collectors.toList()); assertEquals(2, listSizes.size()); System.out.println(listSizes); } @Test public void testMapWithPOJO() { Function<POJO, Integer> countNameLength = POJO::nameWordCount; List<Integer> nameCounts = listOfObjects.stream().map(countNameLength).collect(Collectors.toList()); assertEquals(nameCounts.size(), listOfObjects.size()); nameCounts.forEach(s -> System.out.println(s)); } @Test(expected = NullPointerException.class) public void testMapWithPOJO_Exception() { Function<POJO, Integer> transform = POJO::nameWordCount; listOfObjectWithNullNames.stream().map(transform).collect(Collectors.toList()); } @Test public void testMapWithPOJO_handleRuntimeException() { Function<POJO, Integer> transform = this::countNameLength; List<Integer> nameCounts = listOfObjectWithNullNames.stream().map(transform).collect(Collectors.toList()); assertEquals(nameCounts.size(), listOfObjectWithNullNames.size()); nameCounts.forEach(s -> System.out.println(s)); } @Test public void testMapWithString() { Function<String, String> toUpper = String::toUpperCase; List<String> allUppercase = lowerCaseStrings.stream().map(toUpper).collect(Collectors.toList()); assertEquals(lowerCaseStrings.size(), allUppercase.size()); assertTrue(allUppercase.contains("MARY")); assertTrue(allUppercase.contains("SOMETHING")); assertTrue(allUppercase.contains("END")); } }
Execute the Junit test and capture the output here:
Running jcg.zheng.demo.MapTest [3, 3] 4 5 3 7 0 5 3 7 Tests run: 6, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.179 sec Results : Tests run: 6, Failures: 0, Errors: 0, Skipped: 0
4.3 FlatMapTest
In this step, I will create a FlatMapTest
class which has five test methods:
testFlatMapWith2DArray
– converts a 2D array into aList<String>
testFlatMapWithListofList
– converts aList
ofList<String>
intoList<String>
. Flattening into a list of String.testFlatMapWithListStream
– transformsList
ofList<String>
to aList<String>
testFlatMapWithUpperCaseString
– transforms to uppercase String.
FlatMapTest.java
package jcg.zheng.demo; import static org.junit.Assert.*; import java.util.Arrays; import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; import org.junit.Test; public class FlatMapTest extends CommonData { private Stream<String> buildStreamOfUpperCaseString(List<String> item) { String[] test = new String[item.size()]; for (int i = 0; i < test.length; i++) { test[i] = item.get(i).toUpperCase(); } return Stream.of(test); } @Test public void testFlatMapWith2DArray() { Stream<String> stringStream = Arrays.stream(string2DArray).flatMap(Arrays::stream); assertEquals(3, string2DArray.length); assertEquals(6, stringStream.collect(Collectors.toList()).size()); } @Test public void testFlatMapWithListofList() { Function<List<String>, Stream<String>> toUpperFlatMapFunction = this::buildStreamOfUpperCaseString; Stream.of(stringList2, stringList1, lowerCaseStrings).flatMap(toUpperFlatMapFunction) .forEach(System.out::println); } @Test public void testFlatMapWithListStream() { Stream<String> stringStream = Stream.of(stringList2, stringList1).flatMap(List::stream); assertEquals(3, stringList2.size()); assertEquals(3, stringList1.size()); assertEquals(6, stringStream.collect(Collectors.toList()).size()); } @Test public void testFlatMapWithLongStream() { Function<List<String>, Stream<Long>> countFlatMapFunction = item -> Stream.of(item.stream().count()); Stream.of(stringList2, stringList1, lowerCaseStrings).flatMap(countFlatMapFunction) .forEach(System.out::println); } @Test public void testFlatMapWithUpperCaseString() { Function<String, Stream<String>> toUppderFlatMapFunction = item -> Stream.of(item.toUpperCase()); stringList2.stream().flatMap(toUppderFlatMapFunction).forEach(System.out::println); } }
Execute it as unit test and capture output here.
Running jcg.zheng.demo.FlatMapTest 3 3 3 X Y Z X Y Z A B C MARY SOMETHING END Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.194 sec Results : Tests run: 5, Failures: 0, Errors: 0, Skipped: 0
4.4 BothTest
Both map()
and flatMap()
return a Stream, so they can chain together too. In this step, I will create a BothTest
class which has two test methods:
flatMap_Map
– chainsmap()
afterflatMap()
.map_flatMap
– chainsflatMap()
aftermap()
.
BothTest.java
package jcg.zheng.demo; import static org.junit.Assert.assertEquals; import java.util.Collection; import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; import org.junit.Test; public class BothTest extends CommonData { @Test public void flatMap_Map() { List<String> flatedList = listOfStringLists.stream().flatMap(Collection::stream).map(String::toUpperCase) .collect(Collectors.toList()); assertEquals(2, listOfStringLists.size()); assertEquals(6, flatedList.size()); } @Test public void map_flatMap() { Function<String, Stream<String>> toUpper = item -> Stream.of(item.toUpperCase()); lowerCaseStrings.stream().map(String::toLowerCase).flatMap(toUpper).forEach(item -> { System.out.println(item); }); ; } }
Execute the Junit test and capture the output here.
Running jcg.zheng.demo.BothTest MARY SOMETHING END Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.285 sec Results : Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
5. Summary
In this example, I demonstrated how to use the map and flatMap methods. They are very similar functions. The difference between them is that the mapper
function in flatMap()
returns a Stream
while the mapper
function of map()
return an object.
6. Download the Source Code
This example consists of a Maven project which contains several Junit tests to demonstrate the differences between Stream's map()
and flatMap()
methods.
You can download the full source code of this example here: The Difference Between map() and flatMap() Methods in Java