作为有经验的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 进入链式链路。
读了本文,相信大家对此有了深入的理解。
并能够在工作中做到举一反三,灵活落地各种复杂业务场景。