flatMap是处理多对多关系的核心工具,通过“映射出流、再压平”两步语义展开嵌套集合,如订单→订单项→商品,并需防范null、保持语义清晰及避免副作用。

Java Stream 的 flatMap 是处理多对多关系的核心工具——它能把“集合的集合”压平成单层流,从而自然表达关联实体的展开逻辑。关键不在嵌套调用,而在理解“映射出流、再压平”的两步语义。
用 flatMap 展开一对多,是处理多对多的第一步
多对多本质上是“一个 A 关联多个 B,每个 B 又关联多个 C”,需拆解为两层一对多。先从 A→B 展开,再对每个 B 做 B→C 展开。flatMap 正适合做这种“生成并展平子流”的操作。
例如:订单(Order)与商品(Product)是多对多,中间有订单项(OrderItem)。我们想获取每个订单下所有关联的商品名称:
List<Order> orders = ...;
List<String> allProductNames = orders.stream()
.flatMap(order -> order.getOrderItems().stream()) // 展开所有订单项
.flatMap(item -> item.getProducts().stream()) // 展开每个订单项下的商品
.map(Product::getName) // 提取名称
.collect(Collectors.toList());
避免 null 和空集合导致的异常
flatMap 内部流不能为 null,否则抛 NullPointerException;空集合虽安全,但易被忽略导致结果遗漏。推荐统一用 Optional 或空集合兜底:
立即学习“Java免费学习笔记(深入)”;
- 用
Objects.requireNonNullElse(list, Collections.emptyList())替换可能为 null 的集合 - 或提前过滤:
.filter(Objects::nonNull).flatMap(...) - 更简洁写法:
.flatMap(item -> Optional.ofNullable(item.getProducts())
.map(List::stream).orElse(Stream.empty()))
组合映射时保持业务语义清晰
不要把多层 flatMap 写成一行“面条代码”。可提取中间步骤为具名函数,提升可读性与复用性:
Function<Order, Stream<Product>> orderToAllProducts = order ->
order.getOrderItems().stream()
.flatMap(item -> Optional.ofNullable(item.getProducts())
.map(List::stream).orElse(Stream.empty()));
List<Product> allProducts = orders.stream()
.flatMap(orderToAllProducts)
.collect(Collectors.toList());
这样既保留了 flatMap 的平铺能力,又让“订单 → 所有商品”这一业务含义一目了然。
注意 flatMap 的延迟执行与副作用边界
flatMap 内部创建的 Stream 不会立即执行,整个链路是惰性的。但如果在 flatMap 中做数据库查询、IO 或修改外部状态,就容易因多次遍历或并行流引发重复调用或线程安全问题。
- 确保内部流的生成逻辑无副作用(如不修改原对象、不发 HTTP 请求)
- 若必须查库,先用
collect落地为 List,再 stream 处理 - 并行流中 flatMap 内部不应依赖共享变量


















