2. 1. 概述

map()flatMap() 这两个 API 源自函数式编程语言。在 Java 8 中,我们可以在 OptionalStreamCompletableFuture(虽然名称略有不同)中找到它们。

  • Stream 表示对象序列
  • Optional 表示可能存在或缺失的值类

两者都提供了 map()flatMap() 方法作为聚合操作的一部分。尽管返回类型相似,但它们的行为差异显著。下面通过分析 StreamOptional 的示例来解释这些区别。

2. 2. Optional中的map和flatMap

当函数返回我们需要的精确类型时,map() 方法在 Optional 中表现良好:

Optional<String> s = Optional.of("test");
assertEquals(Optional.of("TEST"), s.map(String::toUpperCase));

但在更复杂的场景中,如果函数也返回 Optional,使用 map() 会导致嵌套结构——因为 map() 内部会进行额外包装:

assertEquals(Optional.of(Optional.of("STRING")), 
  Optional
  .of("string")
  .map(s -> Optional.of("STRING")));

✅ 结果:得到嵌套的 Optional<Optional<String>>
❌ 问题:结构臃肿且未增强空安全性

这种嵌套结构虽然可用,但操作繁琐且没有额外价值。此时 flatMap() 就派上用场了:

assertEquals(Optional.of("STRING"), Optional
  .of("string")
  .flatMap(s -> Optional.of("STRING")));

⚠️ 核心区别:flatMap() 会自动解包嵌套的 Optional,保持扁平结构。

2. 3. Stream中的map和flatMap

两者在 Stream 中的行为逻辑与 Optional 类似:

  • map():将结果包装成新的 Stream
  • flatMap():避免产生嵌套的 Stream<Stream<R>> 结构

简单场景下 map() 表现良好:

List<String> myList = Stream.of("a", "b")
  .map(String::toUpperCase)
  .collect(Collectors.toList());
assertEquals(asList("A", "B"), myList);

但当输入是嵌套结构(如列表的列表)时,map() 就不够用了:

List<List<String>> list = Arrays.asList(
  Arrays.asList("a"),
  Arrays.asList("b"));
System.out.println(list); // 输出: [[a], [b]]

此时 flatMap() 能轻松解决嵌套问题:

System.out.println(list
  .stream()
  .flatMap(Collection::stream)
  .collect(Collectors.toList())); // 输出: [a, b]

✅ 关键机制:

  1. flatMap() 先将输入的 Stream<Stream<T>> 扁平化为 Stream<T>
  2. 再应用类似 map() 的转换逻辑

2. 4. 总结

Java 8 引入的 map()flatMap() 继承自函数式编程思想,可在 StreamOptional 上调用。它们通过应用映射函数帮助获取转换后的对象,但需注意:

场景 map() flatMap()
Optional嵌套 ❌ 产生 Optional<Optional<T>> ✅ 自动解包保持扁平
Stream嵌套 ❌ 产生 Stream<Stream<T>> ✅ 自动扁平化
简单转换 ✅ 直接使用 ⚠️ 过度设计

踩坑提醒:在处理可能产生嵌套结构的场景时,优先考虑 flatMap(),避免后续手动解包的麻烦。

本文示例代码可在 GitHub 获取。


原始标题:The Difference Between map() and flatMap()