Java 8 中的 map() 和 flatMap() 方法有啥区别?

Posted

技术标签:

【中文标题】Java 8 中的 map() 和 flatMap() 方法有啥区别?【英文标题】:What's the difference between map() and flatMap() methods in Java 8?Java 8 中的 map() 和 flatMap() 方法有什么区别? 【发布时间】:2014-12-28 08:34:34 【问题描述】:

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

【问题讨论】:

类型签名有点讲述了整个故事。 map :: Stream T -> (T -> R) -> Stream R, flatMap :: Stream T -> (T -> Stream R) -> Stream R. fwiw,这些类型签名甚至不像 Java。 (我知道,我知道——但要说它讲述了“整个故事”wrt map/flatMap 假设对新的和改进的“Java++”有很多了解) @michael 该类型签名看起来像 Haskell,而不是 Java。但尚不清楚实际的 Java 签名是否更具可读性:<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper) 哈,是的,我指的是“实际的 Java”。与 C++ 一样,任何在 90 年代开始使用现代 Java 的人几乎都认不出来(就像我一样,这两种语言都是如此)。只是回复评论,该方法签名几乎不能说明“整个故事”,至少现在不再,没有额外的说明(或在评论者的情况下,翻译)。 也就是说,map 的映射器 lambda 返回 RflatMap 的映射器 lambda 返回 RStream (Stream<R>)。 flatMap 的映射器返回的流被有效地连接起来。否则,mapflatMap 都返回 Stream<R>;区别在于映射器 lambda 的返回值,RStream<R> 【参考方案1】:

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

如果您有 flat 元素列表,请使用 map:[0, 1, 2, 3, 4, 5] 如果您有一个元素列表,请使用flatMap:[[1, 3, 5], [2, 4, 6]]。这意味着,您的列表需要展平,然后才能将 ma​​p 操作应用于每个元素

【讨论】:

【参考方案2】:

我是这样理解的。

地图:

平面地图:

【讨论】:

【参考方案3】:

ma​​p() 和 flatMap()

    map()

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

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

它只需要类型 Integer 的元素 1 到 5,使用每个元素从类型 String 构建一个值为 "prefix_"+integer_value 的新元素并将其打印出来。

    flatMap()

知道 flatMap() 采用函数 F<T, R> where 是很有用的

T 是一种来自的类型,可以从中/使用它来构建流。它可以是列表 (T.stream())、数组 (Arrays.stream(someArray)) 等。Stream 可以包含/或形成的任何内容。在下面的示例中,每个开发人员都有多种语言,因此 dev. Languages 是一个 List,将使用 lambda 参数。

R 是将使用 T 构建的结果流。知道我们有许多 T 实例,我们自然会有许多来自 R 的流。来自类型 R 的所有这些流现在将合并为一个 来自 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_dlang_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()

当流中的每个 T 类型元素都应该映射/转换为 R 类型的 单个 元素时,请使用 map()。结果是类型为 (1开始元素 -> 1 个结束元素) 并返回 R 类型的新元素流。

当流中的每个 T 类型元素都应该映射/转换为 R 类型元素的 Collections 时,请使用 flatMap()。结果是类型为 (1开始元素 -> n 个结束元素)。然后将这些集合合并(或展平)到新的 R 类型元素流。这对于表示 嵌套循环 非常有用。

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

【讨论】:

【参考方案4】:

.map 用于A -&gt; B 映射

Stream.of("dog", "cat")              // stream of 2 Strings
    .map(s -> s.length())            // stream of 2 Integers: [3, 3]

它将任何项目 A 转换为任何项目 B。 Javadoc


.flatMap 用于A -&gt; Stream&lt; B&gt; 连接

Stream.of("dog", "cat")             // stream of 2 Strings
    .flatMapToInt(s -> s.chars())   // stream of 6 ints:      [d, o, g, c, a, t]

它 --1 将任何项目 A 转换为 Stream&lt; B&gt;,然后 --2 将所有流连接成一个(平面)流。 Javadoc


注意 1:虽然后一个示例扁平化为原始流 (IntStream) 而不是对象流 (Stream),但它仍然说明了 .flatMap 的思想。

注意 2:尽管名称如此,但 String.chars() 方法返回整数。所以实际的集合是:[100, 111, 103, 99, 97, 116] , 其中100'd' 的代码,111'o' 的代码等。同样,为了说明目的,它表示为 [d, o, g, c, a, t]。

【讨论】:

我比其他人更喜欢这个答案。 只需替换为 toCharArray 并缩短您的帖子。【参考方案5】:

我想举两个例子来获得一个更多实用的观点: 第一个使用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);

在第一个示例中没有什么特别之处,应用 Function 以返回大写的 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 流。 它不是整数流! 如果必须使用转换函数(通过映射),则首先必须将 Stream 展平为其他东西(整数 Stream)。 如果 flatMap 被删除,则返回以下错误:运算符 + 未定义参数类型 List, int。 不可能在 List 的整数上应用 + 1!

【讨论】:

@PrashanthDebbadwar 我认为您最终会得到Stream&lt;Integer&gt; 的流,而不是Integer 的流。【参考方案6】:

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


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

flatmap():取一个elements/streams的流和一个函数(显式定义),将函数应用于每个流的每个元素,并收集所有中间结果流,使其成为更大的流(“扁平化”)。如果任何元素的处理导致null,则将空流提供给“展平”的最后一步。如果输入是多个流,则结果流中的元素数是所有输入中所有参与元素的总和。

【讨论】:

【参考方案7】:

请完整阅读帖子以获得清晰的想法,

地图与平面地图:

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

下面给出的短版

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

没有平面图 => [1,2],[1,1] => [[1,2 ],[1,1]] 这里两个列表放在一个列表中,所以输出将是包含列表的列表

平面图 => [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为每个单词返回一个String数组,所以map方法返回的流实际上是Stream类型,但是我们需要的是Stream来表示一个字符流,下面图片说明了问题。

图A:

你可能认为,我们可以使用 flatmap 解决这个问题, 好的,让我们看看如何使用 mapArrays.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());

上面还是不行,因为我们现在得到了一个流列表(更准确地说,Stream>),相反,我们必须先将每个单词转换成一个单独的字母数组,然后把每个数组变成一个单独的流

通过使用 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 中的地图进行比较。 图B

flatMap 方法可让您将一个流的每个值替换为另一个流,然后将所有生成的流连接到一个流中。

【讨论】:

很好的图解说明。 @TechDog 我很欣赏你的例子。我一直在寻找如何将行拆分成单词并流式传输它们。 感谢您基于实例和插图的解释。对理解很有帮助。【参考方案8】:

单行答案:flatMap 有助于将 Collection&lt;Collection&lt;T&gt;&gt; 扁平化为 Collection&lt;T&gt;。同样,它也会将 Optional&lt;Optional&lt;T&gt;&gt; 扁平化为 Optional&lt;T&gt;

如您所见,仅使用 map()

中间类型是Stream&lt;List&lt;Item&gt;&gt; 返回类型为List&lt;List&lt;Item&gt;&gt;

并与 flatMap()

中间类型是Stream&lt;Item&gt; 返回类型为List&lt;Item&gt;

这是来自下面使用的代码的测试结果

-------- Without flatMap() -------------------------------
     collect() returns: [[Laptop, Phone], [Mouse, Keyboard]]

-------- With flatMap() ----------------------------------
     collect() returns: [Laptop, Phone, Mouse, Keyboard]

使用的代码

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

public class Parcel 
  String name;
  List<String> items;

  public Parcel(String name, String... items) 
    this.name = name;
    this.items = Arrays.asList(items);
  

  public List<String> getItems() 
    return items;
  

  public static void main(String[] args) 
    Parcel amazon = new Parcel("amazon", "Laptop", "Phone");
    Parcel ebay = new Parcel("ebay", "Mouse", "Keyboard");
    List<Parcel> parcels = Arrays.asList(amazon, ebay);

    System.out.println("-------- Without flatMap() ---------------------------");
    List<List<String>> mapReturn = parcels.stream()
      .map(Parcel::getItems)
      .collect(Collectors.toList());
    System.out.println("\t collect() returns: " + mapReturn);

    System.out.println("\n-------- With flatMap() ------------------------------");
    List<String> flatMapReturn = parcels.stream()
      .map(Parcel::getItems)
      .flatMap(Collection::stream)
      .collect(Collectors.toList());
    System.out.println("\t collect() returns: " + flatMapReturn);
  

【讨论】:

非常清晰的例子......,用你的例子理解这个概念不会超过几秒钟...... 很好的解释。非常感谢简单和最佳的解释 感谢您提到 Optional> 到 Optional。否则,它会被我回答!【参考方案9】:

简单的答案。

map 操作可以产生StreamStream.EX Stream&lt;Stream&lt;Integer&gt;&gt;

flatMap 操作只会产生Stream 的东西。前Stream&lt;Integer&gt;

【讨论】:

【参考方案10】:

flatMap() 还利用了流的部分惰性求值。它将读取第一个流,并且仅在需要时才会转到下一个流。此处详细解释了该行为:Is flatMap guaranteed to be lazy?

【讨论】:

【参考方案11】:

我不太确定我应该回答这个问题,但每次我面对不理解这一点的人时,我都会使用相同的例子。

想象你有一个苹果。例如,map 正在将该苹果转换为 apple-juice一对一 映射。

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

【讨论】:

对于flatMap 案例,您是否首先将每个苹果的种子分别装在单独的袋子中,每个苹果一个袋子,然后再将所有袋子倒入一个袋子中? @DerekMahar 在java-10之前它曾经是个穷人,意思是flatmap并不是真的很懒,但是自从java-10之后它就很懒了 @Eugene 请解释一个更懒惰的概念,你试图解释的我不清楚。我明白 derkerMahar 在评论中解释的是 java10 之前发生了什么? @JAVA 搜索flatMap + lazy,我打赌会有一些答案。 @Eugene - flatMap 将一次取一个苹果,提取其种子并将其放入袋子/集合/数组中,直到提取所有苹果种子。正确的 ?流是这样工作的吗?【参考方案12】:

地图:- 此方法将一个 Function 作为参数,并返回一个新流,该流包含通过将传递的函数应用于流的所有元素而生成的结果。

让我们想象一下,我有一个整数值列表( 1,2,3,4,5 )和一个函数接口,其逻辑是传递整数的平方。 ( 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);

输出:-

[1, 4, 9, 16, 25]

如您所见,输出是一个新流,其值是输入流值的平方。

[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/

平面图:- 该方法接受一个函数作为参数,该函数接受一个参数 T 作为输入参数,并返回一个参数流 R 作为返回值。当此函数应用于此流的每个元素时,它会生成一个新值流。然后将每个元素生成的这些新流的所有元素复制到一个新流中,这将是该方法的返回值。

让我们想象一下,我有一个学生对象列表,每个学生可以选择多个科目。

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

输出:-

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

如您所见,输出是一个新流,其值是输入流的每个元素返回的流的所有元素的集合。

[ S1 , S2 , S3 ] -> [ "history","math","geography", "economics","biology", "science","math" ] -> 选择独特的科目 -> [经济学、生物学、地理学、科学、历史、数学]

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

【讨论】:

如果您提供代码而不是仅仅提供文档链接,可能会有所作为【参考方案13】:

流操作flatMapmap 接受一个函数作为输入。

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

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

【讨论】:

【参考方案14】:

我觉得这里的大多数答案都使简单的问题过于复杂。如果您已经了解 map 的工作原理,那应该很容易掌握。

在某些情况下,使用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)

【讨论】:

抱歉,从第 1 点开始的第二个 sn-p 没有编译而不是 List&lt;Integer&gt; result = Stream.of(Arrays.asList(1), Arrays.asList(2, 3)) .flatMap(i -&gt; i) .collect(Collectors.toList()); 。应该是Stream.of(Arrays.asList(1), Arrays.asList(2, 3)) .flatMap(List::stream) .collect(Collectors.toList()); @arthur 我想我在这里使用了 Vavr 的 Stream 和 List - 但我同意这可能会有点混乱 - 我会将其更改为标准 Java @GrzegorzPiwowarek how about this simple explanation ?【参考方案15】:

这对初学者来说非常混乱。基本区别是map 为列表中的每个条目发出一个项目,flatMap 基本上是map + flatten 操作。更清楚地说,当您需要多个值时使用 flatMap,例如,当您期望循环返回数组时,flatMap 在这种情况下将非常有用。

我写了一篇关于这个的博客,你可以看看here。

【讨论】:

how about this simple explanation ?【参考方案16】:

对于 Map,我们有一个元素列表和一个 (function,action) f 所以:

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

对于平面地图,我们有一个元素列表,我们有一个 (function,action) f,我们希望结果是扁平的:

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

【讨论】:

【参考方案17】:

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

【讨论】:

【参考方案18】:

Oracle 关于 Optional 的文章强调了 map 和 flatmap 之间的这种区别:

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

很遗憾,此代码无法编译。为什么?可变计算机 类型为Optional&lt;Computer&gt;,因此调用 地图方法。但是,getSoundcard() 返回类型为的对象 可选的。这意味着映射操作的结果是 Optional&lt;Optional&lt;Soundcard&gt;&gt; 类型的对象。结果,调用 getUSB() 无效,因为最外面的 Optional 包含作为它的 值另一个 Optional,当然不支持 getUSB() 方法。

对于流,flatMap 方法将函数作为参数, 它返回另一个流。此功能适用于每个元素 的流,这将导致流的流。然而, flatMap 具有将每个生成的流替换为 该流的内容。换句话说,所有单独的流 由函数生成合并或“扁平化”为一个 单流。我们在这里想要的是类似的东西,但是 我们想要 将两级 Optional “扁平化”为一个

Optional 还支持 flatMap 方法。其目的是申请 Optional 值的转换函数(就像地图 操作),然后 将生成的两级 Optional 展平为 一个

因此,为了使我们的代码正确,我们需要将其重写如下 平面地图:

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

第一个 flatMap 确保返回 Optional&lt;Soundcard&gt; 而不是Optional&lt;Optional&lt;Soundcard&gt;&gt;,以及第二个 flatMap 达到同样的目的返回一个Optional&lt;USB&gt;。请注意, 第三个调用只需要一个 map() 因为 getVersion() 返回一个 字符串而不是可选对象。

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

【讨论】:

问题是关于 Stream.map 和 Stream.flatMap 而不是关于 Optional.map 和 Optional.flatMap 但这对我理解可选和平面图的问题帮助很大,非常感谢! @djames,这是一个完全有效的答案,从“使用流,flatMap 方法将函数作为参数...”段落开始阅读:) 我认为这是对这里其他一些答案的非常有用的补充。 flatMap() 版本也会在 soundCard 为 null 时抛出 nullpointerexception。那么 Optional 承诺的好处在哪里?【参考方案19】:

mapflatMap 都可以应用于Stream&lt;T&gt;,并且它们都返回Stream&lt;R&gt;。区别在于map 操作为每个输入值生成一个输出值,而flatMap 操作为每个输入值生成任意数量(零个或多个)值。

这反映在每个操作的参数中。

map 操作采用Function,它为输入流中的每个值调用并产生一个结果值,该值被发送到输出流。

flatMap 操作采用一个在概念上想要消耗一个值并产生任意数量的值的函数。但是,在 Java 中,方法返回任意数量的值很麻烦,因为方法只能返回零或一个值。可以想象一个 API,其中 flatMap 的映射器函数接受一个值并返回一个数组或一个 List 值,然后将其发送到输出。鉴于这是流库,表示任意数量的返回值的一种特别恰当的方法是映射器函数本身返回一个流!映射器返回的流中的值从流中排出并传递给输出流。每次调用映射器函数返回的“团块”值在输出流中根本没有区别,因此输出被称为“扁平化”。

典型用途是flatMap 的映射器函数在它想要发送零值时返回Stream.empty(),或者在它想要返回多个值时返回Stream.of(a, b, c) 之类的东西。但是当然可以返回任何流。

【讨论】:

在我看来,flatMap 操作与 flat 完全相反。再一次,把它留给计算机科学家来改变一个术语。就像一个函数是“透明的”,意味着你看不到它所做的任何事情,只能看到结果,而通俗地说你希望一个过程是透明的意味着你希望它的每个部分都被看到。 @coladict 尝试从不同的角度查看它:这不是一个透明的案例,您可以通过它看到内部工作原理,但整个功能本身对您来说是透明的,即不可见 - 同时仍在执行他们的工作,让你看到你正在使用什么。在这种情况下,“flat”指的是“nested”的反义词,flatmap 通过展平来移除一个嵌套级别。 @coladict “透明”的东西多年来一直困扰着我。很高兴知道至少还有一个人有同样的感觉。 扁平化来自将 2 级结构转变为单级结构,请参阅 Dici 的答案以获取示例 ***.com/a/26684582/6012102 这是对flatMap最好的解释。这就是所有点击的原因: 映射器返回的流中的值从流中排出并传递到输出流。每次调用映射器函数返回的“团块”值在输出流中根本没有区别,因此输出被称为“扁平化”。谢谢!【参考方案20】:

Stream.flatMap,正如它的名字一样,是mapflat 操作的组合。这意味着您首先将函数应用于元素,然后将其展平。 Stream.map 只对流应用一个函数而不扁平化流。

要了解扁平化流包含什么,请考虑像[ [1,2,3],[4,5,6],[7,8,9] ] 这样具有“两个级别”的结构。扁平化意味着将其转换为“一层”结构:[ 1,2,3,4,5,6,7,8,9 ]

【讨论】:

简单又甜美 哈哈,说句公道话,看到这个问题获得了多少流量,我仍然感到惊讶。另一个有趣的观察是,我写这个答案已经快 5 年了,并且有一个相当一致的投票模式,我的答案每得到一个答案,接受的答案就会得到大约两个投票。出人意料地一致。 这不是公认的答案,感谢您直截了当并举了一个非常简单的例子 我相信这个答案比接受的答案更简洁,逻辑上更好。 喜欢你简短而甜蜜的例子。谢谢!【参考方案21】:

您传递给stream.map 的函数必须返回一个对象。这意味着输入流中的每个对象都会在输出流中产生一个对象。

您传递给stream.flatMap 的函数为每个对象返回一个流。这意味着该函数可以为每个输入对象返回任意数量的对象(包括无对象)。然后将生成的流连接到一个输出流。

【讨论】:

为什么要“为每个输入对象返回任意数量的对象(包括无)”? @DerekMahar 这会有很多用例。例如,假设您的组织中有Departments 流。每个部门有 0 到 n 个Employees。您需要的是所有员工的流。所以你会怎么做?您编写一个 flatMap 方法,该方法接受一个部门并返回其员工流。 Philipp,您的示例是否说明了使用flatMap 的主要原因?我怀疑这可能是偶然的,并没有说明关键用例或flatMap 存在的原因。 (下面继续...) 在阅读dzone.com/articles/understanding-flatmap 之后,我认为flatMap 背后的主要动机是适应使用map 时可能出现的错误。您如何处理原始集合中的一个或多个项目无法映射到输出项目的情况?通过为每个输入对象引入一个中间集(比如OptionalStream),flatMap 允许您排除“无效”输入对象(或本着***.com/a/52248643/107158 的精神所谓的“坏苹果”) ) 来自最后一组。 @DerekMahar 是的,每个输入对象可能返回或不返回输出对象的情况是平面地图的另一个很好的用例。

以上是关于Java 8 中的 map() 和 flatMap() 方法有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章

面试官:Java 8 map 和 flatMap 的区别?大部分人答不上来

java1.8 新特性(五 如何使用filter,limit ,skip ,distinct map flatmap ,collect 操作 java集合)

java8中的map与flatmap

第三章 flink流处理API - map和flatmap

FlatMap vs Filter,Map Java [重复]

Java8中map()和flatMap()的区别