在Java 8中,Stream.map()和Stream.flatMap()方法之间有什么区别?


当前回答

map()和flatMap()

map ()

只接受一个函数<T, R>一个lambda参数,其中T是元素,R是使用T构建的返回元素。最后,我们将有一个带有类型为R的对象的流。一个简单的例子可以是:

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

它只是取Type Integer的元素1到5,使用每个元素从String类型中构建一个值为“prefix_”+integer_value的新元素,并打印出来。

flatMap ()

知道flatMap()接受一个函数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.

例子

Bachiri Taoufiq的例子(见这里的答案)1简单易懂。为了清晰起见,假设我们有一个开发团队:

dev_team = {dev_1,dev_2,dev_3}

,每个开发人员都懂多种语言:

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

在dev_team上应用Stream.map()来获取每个开发人员的语言:

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

会给你这样的结构:

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

它基本上是一个List<List<Languages>> /Object[Languages[]]。不是很漂亮,也不像java8 !!

使用Stream.flatMap(),你可以“扁平化”的东西,因为它采取上述结构 并将其转换为{lang_a, lang_b, lang_c, lang_d, lang_e, lang_f},基本上可以作为List<Languages>/Language[]/etc…

所以最后,你的代码会像这样更有意义:

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();

或者仅仅是:

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

何时使用map()和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.

Java 8 之前:

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

后Java 8

myFoos
    .stream()
    .flatMap(foo -> foo.getMyBars().stream())
    .forEach(bar -> System.out.println(bar.getMyName()));

其他回答

如果你熟悉c#也可以很好的类比。基本上c# Select类似于java map和c# SelectMany java flatMap。对于集合,同样适用于Kotlin。

流操作flatMap和map接受函数作为输入。

flatMap期望该函数为流的每个元素返回一个新的流,并返回一个流,该流结合了该函数为每个元素返回的流的所有元素。换句话说,使用flatMap,对于来自源的每个元素,函数将创建多个元素。http://www.zoftino.com/java-stream-examples#flatmap-operation

Map期望函数返回一个转换后的值,并返回一个包含转换后元素的新流。换句话说,使用map,对于来自源的每个元素,函数将创建一个转换后的元素。 http://www.zoftino.com/java-stream-examples#map-operation

通过阅读所有的信息,简单的理解方法是:

如果你有一个元素的平面列表,请使用map: [0,1,2,3,4,5] 如果你有一个元素的列表,请使用flatMap:[[1,3,5],[2,4,6]]。这意味着,在映射操作应用于每个元素之前,您的列表需要被平铺

Oracle关于Optional的文章强调了map和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");

第一个flatMap确保返回Optional<Soundcard> 而不是一个Optional<Optional<Soundcard>>,和第二个flatMap 实现相同的目的,返回Optional<USB>。注意 第三个调用只需要一个map(),因为getVersion()返回一个 字符串而不是可选对象。

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

我有一种感觉,这里的大多数答案都把简单的问题复杂化了。如果你已经理解了地图是如何工作的,那就很容易掌握了。

在使用map()时,有些情况下我们可能会得到不需要的嵌套结构,flatMap()方法的设计是通过避免换行来克服这一问题。


例子:

1

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

我们可以使用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));

地点:

private Optional<String> findById(Integer id)