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


当前回答

请仔细阅读这篇文章以获得一个清晰的概念,

map vs flatMap:

要从列表中返回每个单词的长度,我们将执行如下操作。

简短版本如下

当我们收集两个列表时,如下所示

没有flat map =>[1,2],[1,1] =>[[1,2],[1,1]]这里在一个列表中放置了两个列表,因此输出将是包含列表的列表

使用flat map =>[1,2],[1,1] =>[1,2,1,1],这里两个列表被平铺,只有值被放在列表中,因此输出将是只包含元素的列表

基本上,它将所有对象合并为一个

##详细版本已给出如下:-

例如:-考虑一个列表[" STACK ", " OOOVVVER "],我们试图返回一个列表像[" STACKOVER "](从该列表中只返回唯一的字母) 最初,我们将执行如下操作,从[" STACK ", " OOOVVVER "]返回一个列表[" STACKOVER "]

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

这里的问题是,传递给map方法的Lambda为每个单词返回一个字符串数组,因此map方法返回的流实际上是流类型,但我们需要的是流来表示字符流,下面的图像说明了这个问题。

图一:

你可能会想,我们可以用flatmap来解决这个问题,让我们看看如何用map和arrays。stream来解决这个问题 首先,你需要一个字符流而不是数组流。有一个叫做Arrays.stream()的方法,它将接受一个数组并生成一个流,例如:

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

上面的方法仍然不起作用,因为我们现在得到了一个流的列表(更准确地说,流>)。相反,我们必须首先将每个单词转换为一个单独的字母数组,然后将每个数组转换为一个单独的流

通过使用flatMap,我们应该能够修复这个问题如下:

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不是用流而是用流的内容来映射每个数组。在使用map(Arrays::stream)时生成的所有单独流被合并成一个流。图B说明了使用flatMap方法的效果。将其与图A中的map进行比较。 图B

flatMap方法允许您用另一个流替换流的每个值,然后将所有生成的流连接到单个流中。

其他回答

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为每个对象返回一个流。这意味着该函数可以为每个输入对象返回任意数量的对象(包括none)。然后将结果流连接到一个输出流。

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

在使用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)

我不太确定我是否应该回答这个问题,但每当我面对不理解这一点的人时,我就用同样的例子。

假设你有一个苹果。例如,地图是将苹果转换为苹果汁或一对一映射。

同样的苹果,只得到种子,这就是flatMap所做的,或者一对多,一个苹果作为输入,许多种子作为输出。

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


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

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