Home » Java » What's the difference between map and flatMap methods in Java 8?

What's the difference between map and flatMap methods in Java 8?

Posted by: admin November 2, 2017 Leave a comment

Questions:

In Java 8, what’s the difference between Stream.map and Stream.flatMap methods?

Answers:

Both map and flatMap can be applied to a Stream<T> and they both return a Stream<R>. The difference is that the map operation produces one output value for each input value, whereas the flatMap operation produces an arbitrary number (zero or more) values for each input value.

This is reflected in the arguments to each operation.

The map operation takes a Function, which is called for each value in the input stream and produces one result value, which is sent to the output stream.

The flatMap operation takes a function that conceptually wants to consume one value and produce an arbitrary number of values. However, in Java, it’s cumbersome for a method to return an arbitrary number of values, since methods can return only zero or one value. One could imagine an API where the mapper function for flatMap takes a value and returns an array or a List of values, which are then sent to the output. Given that this is the streams library, a particularly apt way to represent an arbitrary number of return values is for the mapper function itself to return a stream! The values from the stream returned by the mapper are drained from the stream and are passed to the output stream. The “clumps” of values returned by each call to the mapper function are not distinguished at all in the output stream, thus the output is said to have been “flattened.”

Typical use is for the mapper function of flatMap to return Stream.empty() if it wants to send zero values, or something like Stream.of(a, b, c) if it wants to return several values. But of course any stream can be returned.

Questions:
Answers:

Stream.flatMap, as it can be guessed by its name, is the combination of a map and a flat operation. That means that you first apply a function to your elements, and then flatten it. Stream.map only applies a function to the stream without flattening the stream.

To understand what flattening a stream consists in, consider a structure like [ [1,2,3],[4,5,6],[7,8,9] ] which has “two levels”. Flattening this means transforming it in a “one level” structure : [ 1,2,3,4,5,6,7,8,9 ].

Questions:
Answers:

I would like to give 2 examples to get a more practical point of view:

1st example making usage of map:

@Test
public void convertStringToUpperCaseStreams() {
    List<String> collected = Stream.of("a", "b", "hello") // Stream of String 
            .map(String::toUpperCase) // Returns a stream consisting of the results of applying the given function to the elements of this stream.
            .collect(Collectors.toList());
    assertEquals(asList("A", "B", "HELLO"), collected);
}   

Nothing Special into the 1st example, a Function is applied to return the String in uppercase.


2nd example making usage of flatmap:

@Test
public void testflatMap() throws Exception {
    List<Integer> together = Stream.of(asList(1, 2), asList(3, 4)) // Stream of List<Integer>
            .flatMap(List::stream)
            .map(integer -> integer + 1)
            .collect(Collectors.toList());
    assertEquals(asList(2, 3, 4, 5), together);
}

In the second example, a Stream of List is passed. It is NOT a Stream of Integer!

If a tranformation Function has to be used (through map), then first the Stream has to be flattened to something else (a Stream of Integer).

If flatMap is removed then the following error is returned: The operator + is undefined for the argument type(s) List, int.

It is NOT possible to apply + 1 on a List of Integers!

Questions:
Answers:

Please go through the post fully to get clear idea,
map vs flatMap:
To return a length of each word from a list, we would do something like below..
For example:-
Consider a list [“STACK”,”OOOVVVER”] and we are trying to return a list like [“STACKOVER”](returning only unique letters from that list)
Initially we would do something like below to return a list [“STACKOVER”] from [“STACK”,”OOOVVVER”]

enter image description here

Here the issue is , Lambda passed to the map method returns a String array for each word, So the stream returned by the map method is actually of type Stream, But what we need is Stream to represent a stream of characters, below image illustrates the problem.

Figure A:

enter image description here

You might think that, We can resolve this problem using flatmap,
OK, let us see how to solve this by using map and Arrays.stream
First of all you gonna need a stream of characters instead of a stream of arrays. There is a method called Arrays.stream() that would take an array and produces a stream, for example:
enter image description here

The above still does not work, because we now end up with a list of streams (more precisely, Stream>), Instead, we must first convert each word in to an array of individual letters and then make each array into a separate stream

By using flatMap we should be able to fix this problem as below:

enter image description here

flatMap would perform mapping each array not with stream but with the contents of that stream.All of the individual streams that would get generated while using map(Arrays::stream) get merged in to a single stream. Figure B illustrates the effect of using the flatMap method. Compare it with what map does in figure A.
Figure B
enter image description here

The flatMap method lets you replace each value of a stream with another stream and then joins all the generated streams into a single stream.

Questions:
Answers:

The function you pass to stream.map has to return one object. That means each object in the input stream results in exactly one object in the output stream.

The function you pass to stream.flatMap returns a stream for each object. That means the function can return any number of objects for each input object (including none). The resulting streams are then concatenated to one output stream.

Questions:
Answers:

Oracle’s article on Optional highlights this difference between map and flatmap:

String version = computer.map(Computer::getSoundcard)
                  .map(Soundcard::getUSB)
                  .map(USB::getVersion)
                  .orElse("UNKNOWN");

Unfortunately, this code doesn’t compile. Why? The variable computer
is of type Optional<Computer>, so it is perfectly correct to call the
map method. However, getSoundcard() returns an object of type
Optional. This means the result of the map operation is an
object of type Optional<Optional<Soundcard>>. As a result, the call to
getUSB() is invalid because the outermost Optional contains as its
value another Optional, which of course doesn’t support the getUSB()
method.

With streams, the flatMap method takes a function as an argument,
which returns another stream. This function is applied to each element
of a stream, which would result in a stream of streams. However,
flatMap has the effect of replacing each generated stream by the
contents of that stream. In other words, all the separate streams that
are generated by the function get amalgamated or “flattened” into one
single stream. What we want here is something similar, but we want to
“flatten” a two-level Optional into one
.

Optional also supports a flatMap method. Its purpose is to apply
the transformation function on the value of an Optional (just like the map
operation does) and then flatten the resulting two-level Optional into
a single one
.

So, to make our code correct, we need to rewrite it as follows using
flatMap:

String version = computer.flatMap(Computer::getSoundcard)
                   .flatMap(Soundcard::getUSB)
                   .map(USB::getVersion)
                   .orElse("UNKNOWN");

The first flatMap ensures that an Optional<Soundcard> is returned
instead of an Optional<Optional<Soundcard>>, and the second flatMap
achieves the same purpose to return an Optional<USB>. Note that the
third call just needs to be a map() because getVersion() returns a
String rather than an Optional object.

http://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html

Questions:
Answers:

Map :-
This method takes one Function as an argument and returns a new stream consisting of the results generated by applying the passed function to all the elements of the stream.

Let’s imagine, I have a list of integer values ( 1,2,3,4,5 ) and one function interface whose logic is square of passed integer. ( e -> e * e ).

List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5);

List<Integer> newList = intList.stream().map( e -> e * e ).collect(Collectors.toList());

System.out.println(newList);

output :-

[1, 4, 9, 16, 25]

As you can see, output is a new stream whose values are square of values of input stream.

[1, 2, 3, 4, 5] -> apply e -> e * e -> [ 1*1, 2*2, 3*3, 4*4, 5*5 ] -> [1, 4, 9, 16, 25 ]

http://codedestine.com/java-8-stream-map-method/

FlatMap :-
This method takes one Function as an argument, this function accepts one parameter T as an input argument and return one stream of parameter R as a return value. When this function is applied on each element of this stream, it produces a stream of new values. All the elements of these new streams generated by each element are then copied to a new stream, which will be a return value of this method.

Let’s image, I have a list of student objects, where each student can opt for multiple subjects.

List<Student> studentList = new ArrayList<Student>();

  studentList.add(new Student("Robert","5st grade", Arrays.asList(new String[]{"history","math","geography"})));
  studentList.add(new Student("Martin","8st grade", Arrays.asList(new String[]{"economics","biology"})));
  studentList.add(new Student("Robert","9st grade", Arrays.asList(new String[]{"science","math"})));

  Set<Student> courses = studentList.stream().flatMap( e -> e.getCourse().stream()).collect(Collectors.toSet());

  System.out.println(courses);

output :-

[economics, biology, geography, science, history, math]

As you can see, output is a new stream whose values are collection of all the elements of the streams return by each element of input stream.

[ S1 , S2 , S3 ] ->
[ {“history”,”math”,”geography”}, {“economics”,”biology”}, {“science”,”math”} ] -> take unique subjects ->
[economics, biology, geography, science, history, math]

http://codedestine.com/java-8-stream-flatmap-method/

Questions:
Answers:

I have a feeling that all answers here overengineer the problem. If you already understand how the map works that should be fairly simple to grasp.

The flatmap() method is a special kind of map() which allows to flatten nested structures or avoid ending up with them.


Examples:

1

List<List<Integer>> result = Stream.of(Arrays.asList(1), Arrays.asList(2, 3))
  .collect(Collectors.toList());

We can avoid having nested lists by using flatmap:

List<Integer> result = Stream.of(Arrays.asList(1), Arrays.asList(2, 3))
  .flatMap(i -> i)
  .collect(Collectors.toList());

2

Optional<Optional<String>> result = Optional.of(42)
      .map(id -> findById(id));

Optional<String> result = Optional.of(42)
      .flatMap(id -> findById(id));

where:

private Optional<String> findById(Integer id)

Questions:
Answers:

Also good analogy can be with C# if you familiar with. Basically C# Select similar to java map and C# SelectMany java flatMap. Same applies to Kotlin for collections.

Questions:
Answers:

for a Map we have a list of elements and a (function,action) f so :

[a,b,c] f(x) => [f(a),f(b),f(c)]

and for the flat map we have a list of elements list and we have a (function,action) f and we want the result to be flattened :

[[a,b],[c,d,e]] f(x) =>[f(a),f(b),f(c),f(d),f(e)]

Questions:
Answers:

This is very confusing for beginners. The basic difference is map emits one item for each entry in the list and flatMap is basically a map + flatten operation. To be more clear, use flatMap when you require more than one value, eg when you are expecting a loop to return arrays, flatMap will be really helpful in this case.

I have written a blog about this, you can check it out here.