What's the difference between map and flatMap

2019-01-02 18:57发布

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

18条回答
只靠听说
2楼-- · 2019-01-02 19:47

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.

查看更多
牵手、夕阳
3楼-- · 2019-01-02 19:48

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 in the first example, a Function is applied to return the String in uppercase.

Second 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 transformation 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!

查看更多
无色无味的生活
4楼-- · 2019-01-02 19:48

Please go through the post fully to get a 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”]

public class WordMap {
  public static void main(String[] args) {
    List<String> lst = Arrays.asList("STACK","OOOVER");
    lst.stream().map(w->w.split("")).distinct().collect(Collectors.toList());
  }
}

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:

String[] arrayOfWords = {"STACK", "OOOVVVER"};
Stream<String> streamOfWords = Arrays.stream(arrayOfWords);
streamOfWords.map(s->s.split("")) //Converting word in to array of letters
    .map(Arrays::stream).distinct() //Make array in to separate stream
    .collect(Collectors.toList());

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 into 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:

String[] arrayOfWords = {"STACK", "OOOVVVER"};
Stream<String> streamOfWords = Arrays.stream(arrayOfWords);
streamOfWords.map(s->s.split("")) //Converting word in to array of letters
    .flatMap(Arrays::stream).distinct() //flattens each generated stream in to a single stream
    .collect(Collectors.toList());

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 into 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.

查看更多
心情的温度
5楼-- · 2019-01-02 19:49

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

There are cases where we can end up with unwanted nested structures when using map(), the flatMap() method is designed to overcome this by avoiding wrapping.


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.stream())
  .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)
查看更多
临风纵饮
6楼-- · 2019-01-02 19:50

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.

查看更多
临风纵饮
7楼-- · 2019-01-02 19:51

map() and flatMap()

  1. map()

Just takes a Function a lambda param where T is element and R the return element built using T. At the end we'll have a Stream with objects of Type R. A simple example can be:

Stream
  .of(1,2,3,4,5)
  .map(myInt -> "preFix_"+myInt)
  .forEach(System.out::println);

It simply takes elements 1 to 5 of Type Integer, uses each element to build a new element from type String with value "prefix_"+integer_value and prints it out.

  1. flatMap()

It is useful to know that flapMap() takes a function F<T, R> where

  • T is a type from which a Stream can be built from/with. It can be a List (T.stream()), an array (Arrays.stream(someArray)), etc.. anything that from which a Stream can be with/or form. in the example below each dev has many languages, so dev. Languages is a List and will use a lambda parameter.

  • R is the resulting Stream that will be built using T. Knowing that we have many instances of T, we will naturally have many Streams from R. All these Streams from Type R will now be combined into one single 'flat' Stream from Type R.

Example

The examples of Bachiri Taoufiq see its answer here are simple and easy to understanding. Just for clarity, let just say we have a team of developers:

dev_team = {dev_1,dev_2,dev_3}

, with each developer knowing many languages:

dev_1 = {lang_a,lang_b,lang_c},
dev_2 = {lang_d},
dev_2 = {lang_e,lang_f}

Applying Stream.map() on dev_team to get the languages of each dev:

dev_team.map(dev -> dev.getLanguages())

will give you this structure:

{ 
  {lang_a,lang_b,lang_c},
  {lang_d},
  {lang_e,lang_f}
}

which is basically a List<List<Languages>> /Object[Languages[]]. Not so very pretty, nor Java8-like!!

with Stream.flatMap() you can 'flatten' things out as it takes the above structure
and turns it into {lang_a, lang_b, lang_c, lang_d, lang_e, lang_f}, which can basically used as List<Languages>/Language[]/ect...

so the end your code would make more sense like this:

dev_team
   .stream()    /* {dev_1,dev_2,dev_3} */
   .map(dev -> dev.getLanguages()) /* {{lang_a,...,lang_c},{lang_d}{lang_e,lang_f}}} */
   .flatMap(languages ->  languages.stream()) /* {lang_a,...,lang_d, lang_e, lang_f} */
   .doWhateverWithYourNewStreamHere();

or simply:

dev_team
       .stream()    /* {dev_1,dev_2,dev_3} */
       .flatMap(dev -> dev.getLanguages().stream()) /* {lang_a,...,lang_d, lang_e, lang_f} */
       .doWhateverWithYourNewStreamHere();

When to use map() and use flatMap():

  • Use map() when each element of type T from your stream is supposed to be mapped/transformed to a single element of type R. The result is a mapping of type (1 start element -> 1 end element) and new stream of elements of type R is returned.

  • Use flatMap() when each element of type T from your stream is supposed to mapped/transformed to a Collections of elements of type R. The result is a mapping of type (1 start element -> n end elements). These Collections are then merged (or flattened) to a new stream of elements of type R. This is useful for example to represent nested loops.

Pre Java 8:

List<Foo> myFoos = new ArrayList<Foo>();
    for(Foo foo: myFoos){
        for(Bar bar:  foo.getMyBars()){
            System.out.println(bar.getMyName());
        }
    }

Post Java 8

myFoos
    .stream()
    .flat(foo -> foo.getMyBars().stream())
    .forEach(bar -> System.out.println(bar.getMyName()));
查看更多
登录 后发表回答