FlatMap范式:从 Stream、Optional 到 CompletableFuture 统一编程思想

3 阅读3分钟

作为有经验的Java程序员,第一眼看到FlatMap,一定会想到 Stream api 中的数据转换方法了吧。

可能大家最常用的莫过于map方法了,可能还有不少人对floatMap方法比较陌生。

今天来带大家扒一扒floatMap有什么特殊的能力。

作用

既然有了map,为什么还需要有 flatMap呢?

因为flatMap可以实现,先转换,再扁平化,自动拆一层容器。

来来来,下面看几个例子。

示例1:Stream API

批量订单集合,每个订单包含多个商品,需要提取所有商品,属于多元素嵌套容器场景。

如果只用map,就会是只转换,不拆层。得到的是嵌套的结构Stream<Stream<String>>,比如下面的代码:

Stream<Stream<String>> badStream = orderList.stream()
        .map(order -> order.getGoodsList().stream());

当使用了flatMap后,可以直观的看到,子流已经合并并拆为单层了Stream<String>,代码如下:

List<String> allGoods = orderList.stream()
        .flatMap(order -> order.getGoodsList().stream())
        .collect(Collectors.toList());

示例2:Optional

人员-车辆-保险三层嵌套,每个层级都可能为空,方法返回 Optional,属于单元素空值嵌套场景。

map转换后嵌套,得到Optional<Optional<Insurance>>,无法直接取值

Optional<Optional<Insurance>> badOptional = person.getCar()
        .map(Car::getInsurance);

当使用flatMap后,自动拆除一层,得到单层的结果,就能直接使用了。

Optional<Insurance> goodOptional = person.getCar()
        .flatMap(Car::getInsurance);

在这里,你可能有想用get方法的冲动,直接取值不就行了?

之所以用Optional是因为我们不知道具体有没有对象,直接使用get方法,强行取值就会抛异常,为了避免异常再增加空值判断?那使用Optional就没有意义了。

我们的目标就是全程安全无异常,无需手动判空。

示例3:CompletableFuture

嘿嘿,想不到吧,CompletableFuture也能来这里掺和一下!

异步查询用户、再根据用户异步查询订单,两步异步操作依赖执行,属于异步任务嵌套场景。

当我们使用map时,哦,不,这里应该是thenApply方法,就会产生双层异步容器,无法直接获取结果。

CompletableFuture<CompletableFuture<List<String>>> future1 = CompletableFuture
                .supplyAsync(() -> List.of("1", "2"))
                .thenApply(list -> CompletableFuture.supplyAsync(() -> List.of("a", "b")));

当我们需要拆层时,就可以使用flatMap了,这里对应的是thenCompose方法。

CompletableFuture<List<String>> future2 = CompletableFuture
                .supplyAsync(() -> List.of("1", "2"))
                .thenCompose(list -> CompletableFuture.supplyAsync(() -> List.of("a", "b")));

总结

通过以上的示例,我们可以看到:FlatMap 是通用编程思想,而非单一API:Stream.flatMap、Optional.flatMap、CompletableFuture.thenCompose 是同一范式在不同容器的落地实现,核心作用都是转换+拆层,消除容器嵌套。

不过呢,还是有局限性的:它一次只能拆除一层容器嵌套,如果有多层,需要多次链式调用,可读性大幅下降。

另外,还有空容器异常问题,比如Stream中某个子集合为null,这种神仙来了也不行啊,只能提前做规范化了,禁止 null 进入链式链路。

读了本文,相信大家对此有了深入的理解。

并能够在工作中做到举一反三,灵活落地各种复杂业务场景。