Core Java

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.

map and flatmap - Stream
Figure 1 Stream

In this example, I will demonstrate:

  • How to use map() to transform a String, POJO, and List to another object in 1-to-1 mapping.
  • How to use flatMap() to transform a String, POJO, and List to another Stream 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 a POJO object to Long with the value 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 a List<String>
  • testFlatMapWithListofList – converts a List of List<String> into List<String>. Flattening into a list of String.
  • testFlatMapWithListStream – transforms List of List<String> to a List<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 – chains map() after flatMap().
  • map_flatMap – chains flatMap() after map().

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.

Download
You can download the full source code of this example here: The Difference Between map() and flatMap() Methods in Java

Mary Zheng

Mary has graduated from Mechanical Engineering department at ShangHai JiaoTong University. She also holds a Master degree in Computer Science from Webster University. During her studies she has been involved with a large number of projects ranging from programming and software engineering. She works as a senior Software Engineer in the telecommunications sector where she acts as a leader and works with others to design, implement, and monitor the software solution.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button