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


当前回答

如果您认为map()是一个迭代(一级for循环),那么flatmap()是一个两级迭代(类似于嵌套的for循环)。(输入每个迭代元素foo,然后执行foo. getbarlist()并再次迭代该barList)


Map():获取一个流,对每个元素做一些事情,收集每个进程的单个结果,输出另一个流。“做一些函数”的定义是隐含的。如果任何元素的处理结果为null,则使用null来组成最终流。因此,结果流中的元素数量将等于输入流的数量。

Flatmap():获取元素/流流和函数(显式定义)的流,将函数应用到每个流的每个元素,并将所有中间结果流收集为更大的流(“扁平化”)。如果任何元素的处理结果为null,则为“扁平化”的最后一步提供空流。如果输入是几个流,则结果流中的元素数量是所有输入中所有参与元素的总和。

其他回答

流。flatMap,顾名思义,是映射和平面操作的组合。这意味着您首先将一个函数应用到元素上,然后将其压平。流。Map仅将函数应用于流,而不会将流平展。

为了理解流的扁平化是由什么组成的,考虑一个像[[1,2,3],[4,5,6],[7,8,9]]这样的结构,它有“两个层次”。扁平化意味着将其转换为“一级”结构:[1,2,3,4,5,6,7,8,9]。

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

我想举两个例子来说明更实际的观点: 第一个使用地图的例子:

@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);
}

在第一个例子中没有什么特别的,一个函数被应用来返回大写的String。

第二个使用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);
}

在第二个例子中,传递了一个List流。它不是一个整数流! 如果必须使用转换函数(通过map),则首先必须将流平展为其他类型的流(整数流)。 如果flatMap被移除,则返回以下错误:对于参数类型List, int,操作符+未定义。 不可能在整数列表上应用+ 1 !

flatMap()还利用了流的部分延迟求值。它将读取第一个流,只有在需要时才会进入下一个流。这里详细解释了这种行为:flatMap保证是懒惰的吗?

如果您认为map()是一个迭代(一级for循环),那么flatmap()是一个两级迭代(类似于嵌套的for循环)。(输入每个迭代元素foo,然后执行foo. getbarlist()并再次迭代该barList)


Map():获取一个流,对每个元素做一些事情,收集每个进程的单个结果,输出另一个流。“做一些函数”的定义是隐含的。如果任何元素的处理结果为null,则使用null来组成最终流。因此,结果流中的元素数量将等于输入流的数量。

Flatmap():获取元素/流流和函数(显式定义)的流,将函数应用到每个流的每个元素,并将所有中间结果流收集为更大的流(“扁平化”)。如果任何元素的处理结果为null,则为“扁平化”的最后一步提供空流。如果输入是几个流,则结果流中的元素数量是所有输入中所有参与元素的总和。