Java Stream flatMap()
Overview
Java flatMap is a method that, when applied to a stream of values, maps each value to some required output value. The mapping or transformation of input to output takes place via a mapper, which is a Functional Interface. It is similar to how the map function works. But flatMap is different because it also flattens the result into a single collection of items. It is an intermediate operation. Flattening helps in avoiding nested structures.
Syntax of Java Stream flatMap()
- R - Element type of new Stream
- mapper - A non-interfering, stateless function to apply to each element which produces a stream of new values
- T - Type of existing Stream elements that is provided as input to the mapping function
The Stream interface in Java 8 has three more methods similar to flatMap Java:
- To convert a Stream of items to a Stream of int-valued items.
- To convert a Stream of items to a Stream of long-valued items.
- To convert a Stream of items to a Stream of double-valued items.
Parameter of Java Stream flatMap()
While using Java flatMap, you must supply a single parameter known as mapper. It is a Functional Interface that can accept a lambda expression of type Function, or anything equivalent to it. It performs the mapping operation.
For the sake of understanding, take the mapper type as:
The mapper has the following properties:
- Non-interfering It is the property by which the mapper does not modify the elements in the input Stream. Instead, it will create a different item from each item in the input Stream.
- Stateless To understand this property, consider the opposite situation of adding unique (distinct) numbers to a list. Before adding another number to the list, we check if it was previously processed. It is called Stateful behavior of actions. On the other hand, in Stateless behavior, you don't process the next element based on the already processed items. So every entity of a Stream is processed independently through this mapper. In other words, the same input should always give the same output.
Return Value of Java Stream flatMap()
The Java flatMap returns a new Stream of type R; the method signature discussed in the syntax section of the article highlights this.
The two operations are evident from the name of the method:
flatMap() = map() + flattening
First, the mapping takes place, and finally, the flattening operation.
Example
To grasp the concept of Java flatMap, consider a situation where you have a list of Employees in your company. Each Employee in your company can have one or more phone numbers. An Employee is represented programmatically as an instance of a class named Employee. Each instance stores phone numbers in the form of a list.
Now, you want to generate a list of all phone numbers. Let's understand this through a code example:
Output:
Explanation: This program prepares an array list of Employees by adding employee data. Each time we invoke the stream method on the Employee list, it returns a Stream<Employee>. Using the map method collects phone numbers in a 2-D list. It converts Stream<Employee> to Stream<List<String>> by mapping each Employee to their phone list. Finally, we collect the contents of the Stream in the list. You can flatten this nested structure using Java Stream flatMap. Using flatMap just after the map converts the Stream<List<String>> to Stream<String>. We supply flatMap a mapper that will convert List<String> (the type of item present inside the input Stream) to Stream<String> and also prepends "+91-" to each string. We use this mapper because Java flatMap accepts a stream of objects. Finally, each item of the intermediate result, i.e., Stream<Stream<String>> is flattened to obtain Stream<String>. The resulting stream items are one-by-one collected inside a list.
What is Java Stream flatMap()?
The Java flatMap method performs mapping and flattening. It performs a one-to-many mapping. It is different from Java Stream map that performs one-to-one mapping.
One-to-One mapping involves mapping one value to one and only one value. For example, one-to-one mapping occurs when you try to find out the length of a string. Here, you map each string to a single integer.
One-to-Many mapping involves mapping one value to either zero or more values. For example, one-to-many mapping occurs when you flatten a single stream of strings to zero or more string values.
Java Stream flatMap converts a Stream<Stream<T>> to Stream<R>. This is the flattening operation.
Note: The flattening is the process of converting a 2D collection or Stream (nested structure) into a single collection or Stream (flattened structure). For instance,
List of Lists:
is flattened to
Flattened List:
It is an intermediate operation. Intermediate operations in Java Stream API are always lazy. They do not perform the actual action but only return a new Stream. To understand the concept clearly, you may refer to this.
More Examples
1. Flattening a Nested List to List
Output:
Explanation: We are creating three lists of numbers: evens, odds, and primes. These lists are collected together inside another list. It forms a 2-D list. Next, flatMap is applied to a stream created using this 2-D list. The mapper supplied to flatMap creates a new stream with each number list. It then flattens those streams of numbers into a single stream, and the distinct method only includes unique numbers from the flattened stream. Then the collection of items in the flattened stream takes place using the Collectors.collect() utility method.
2. Flattening a Nested Array to a List
Output:
Explanation: This example is similar to the previous one. We are converting a 2-D string array to a flattened list of strings. Arrays.stream() helps create a new stream from an existing array. The rest of the work is similar to the previous example.
3. Word Count of a Text File
test.txt contents:
Output:
Explanation: In this example, we first store the path of a file for which we want to count the words. Then, we supply the path to the Files.lines() method that splits the file contents into multiple lines and creates a stream of lines (strings). Next, we use this stream of strings with flatMap. It maps each string in the stream to a list of words using a RegEx that splits each string on one or more spaces. It results in Stream<String> inside another Stream. But the Stream<String> is flattened to multiple strings because of Java Stream flatMap, and we obtain a stream of strings (words). The count method returns the total items in a stream. Hence, we get the count of words.
4. Java Stream flatMap with Primitive Types
Output:
Explanation: You can also use any of the specializations of flatMap method to flat the Stream of primitive types. The long-valued number arrays prepared here are included inside a Stream using Stream.of(). The flatMapToLong returns a specialised Stream called LongStream which consists of flattened long-valued numbers.
Stream.flatMap() Vs. Stream.map()
Stream.flatMap() | Stream.map() |
---|---|
It processes a Stream of Stream of objects. | It processes a Stream of objects. |
The mapper you pass to flatMap() operation returns an arbitrary number of values (zero or more) as output for a single value. | The mapper passed to map() operation returns a single value for a single input. |
It performs one-to-many mapping. | It performs one-to-one mapping. |
It is combination of mapping and flattening operations. | It only performs the mapping operation. |
It transforms Stream<Stream<T>> to Stream<R>. | It transforms Stream<T> to Stream<R>. |
Example: List<Integer> allDistinctNums = Stream.of(evens, odds, primes).flatMap(numList -> numList.stream()).distinct().collect(Collectors.toList()); | Example: List<Integer> stringLengths = Stream.of("ab", "abcd", "abc").map(String::length).collect(Collectors.toList()); |
T: Type of object in input Stream R: Type of object in output Stream
Conclusion
- Java flatMap operation can be described as: flatMap = map + flattening operation.
- It is an intermediate operation that is always lazy as described in Java Stream API.
- It converts a nested structure (Stream<Stream<T>) to a flattened one (Stream<R>).
- Java Stream flatMap requires only one parameter called the mapper.
- The mapper has two important properties. It should be Non-Interfering and Stateless.
- flatMap always performs a one-to-many mapping and eliminates nested structures. So it overcomes some of the limitations of the map method.
- There are various specializations of the flatMap method like flatMapToInt, flatMapToLong, and flatMapToDouble.
- Some examples where flatMap can be used are:
- Count of words of a text file.
- Convert a Nested List into a single List.
- Convert a Nested Array into a single List