flatMap用于平铺一对多嵌套对象(如Order→List),先用flatMap展开子集合为流,再用map构造扁平对象OrderItemFlat,需防范null并按层级链式调用。

用 flatMap 处理多表 Join 后的嵌套对象平铺,核心不是真做 SQL Join,而是模拟“一对多”关联关系的数据展开——比如一个订单(Order)对应多个商品(Item),你想把每个 Order+Item 组合成一条扁平记录(如 OrderItemFlat),这就需要 flatMap 配合 map 联动完成。
理解场景本质:不是数据库Join,是对象图展开
Java Stream 不执行 SQL,所谓“多表 Join 后嵌套”,通常指已加载好的对象结构,例如:
-
List<order></order>,每个Order持有List<item></item> - 你希望得到
List<orderitemflat></orderitemflat>,每条含order.id、item.name、item.price等字段
这本质上是“一对多”的笛卡尔展开,flatMap 正为此设计。
标准写法:flatMap 展开子集合 + map 构造扁平对象
以 Order 和 Item 为例:
List<OrderItemFlat> flatList = orders.stream()
.flatMap(order -> order.getItems().stream() // 对每个 order,将其 items 展成流
.map(item -> new OrderItemFlat( // 对每个 item,绑定当前 order 信息构造新对象
order.getId(),
order.getCustomerName(),
item.getName(),
item.getPrice(),
item.getQuantity()
))
)
.collect(Collectors.toList());
关键点:
-
flatMap负责“解包”:把Order → List<item></item>变成Order → Stream<item></item>,再合并所有子流 -
map在 flatMap 内部完成字段组装:能自然访问外层order(闭包捕获),无需额外传参 - 如果某 order.getItems() 为 null 或空,需前置防护:
.filter(order -> order.getItems() != null && !order.getItems().isEmpty())
处理更深层嵌套(如 Order→Item→Sku)
若 Item 还关联 Sku 列表,要展开到三级(Order + Item + Sku),就链式 flatMap:
List<OrderItemSkuFlat> result = orders.stream()
.flatMap(order -> order.getItems().stream()
.flatMap(item -> item.getSkus().stream()
.map(sku -> new OrderItemSkuFlat(
order.getId(),
item.getName(),
sku.getSkuCode(),
sku.getStock()
))
)
)
.collect(Collectors.toList());
注意:
- 每层嵌套调一次
flatMap,顺序不能乱 - 层级加深后可读性下降,建议封装为方法,如
orderToFlatStream(order) - 避免在 flatMap lambda 中做重逻辑(如远程调用),否则影响流的惰性与并行安全
常见陷阱与规避
容易出错的地方:
-
误用 map:写成
.map(order -> order.getItems().stream())→ 得到Stream<stream>></stream>,后续必须再 flatMap,多此一举 -
忽略空值:直接
order.getItems().stream()遇到 null 会抛NullPointerException;稳妥写法:Optional.ofNullable(order.getItems()).orElse(List.of()).stream() -
类型混淆:若
getItems()返回Collection或Set,仍可用.stream(),但注意去重语义是否符合业务
不复杂但容易忽略。


















