JAVA每日一学---CompletableFuture中thenApply与thenCompose区别详解

CompletableFuture 里 thenApply 和 thenCompose 到底有什么区别?

学习 CompletableFuture 的时候,很容易被 thenApplythenCompose 绕晕。尤其是看到这种代码:

复制代码
httpClient.sendAsync(request, BodyHandlers.ofString())
    .thenApply(HttpResponse::body)
    .thenCompose(repository::queryAsync)
    .thenAccept(service::handle);

一开始我也会疑惑:为什么不能都用 thenApply

先记一句话

复制代码
thenApply   里面放普通函数
thenCompose 里面放异步函数

更准确地说:

复制代码
thenApply:   T -> U
thenCompose: T -> CompletionStage<U>

也就是说:

  • thenApply 适合处理返回普通值的函数

  • thenCompose 适合处理返回 CompletableFuture 的函数

thenApply 是干嘛的?

比如:

复制代码
.thenApply(HttpResponse::body)

假设上一步结果是:

复制代码
HttpResponse<String>

HttpResponse::body 返回的是普通值:

复制代码
String

所以整一步的结果会变成:

复制代码
CompletableFuture<String>

注意,不是 body() 返回了 CompletableFuture<String>,而是因为整个异步链本来就是 CompletableFuture,所以 thenApply 会返回一个新的 CompletableFuture 阶段。

thenCompose 是干嘛的?

假设有个异步查询方法:

复制代码
CompletableFuture<Result> queryAsync(String body)

那这句:

复制代码
.thenCompose(repository::queryAsync)

等价于:

复制代码
.thenCompose(body -> repository.queryAsync(body))

这里 thenCompose 做了几件事:

  1. 等上一步的 CompletableFuture<String> 完成

  2. 取出里面真正的 String

  3. 把这个 String 传给 queryAsync

  4. queryAsync 返回 CompletableFuture<Result>

  5. thenCompose 把这个结果接平,最终得到 CompletableFuture<Result>

所以 queryAsync 接收的不是:

复制代码
CompletableFuture<String>

而是里面真正的:

复制代码
String

为什么不能都用 thenApply?

如果写成这样:

复制代码
.thenApply(repository::queryAsync)

queryAsync 返回的是:

复制代码
CompletableFuture<Result>

thenApply 会把它当成一个普通返回值处理,结果就变成:

复制代码
CompletableFuture<CompletableFuture<Result>>

这就多套了一层壳。

而用 thenCompose

复制代码
.thenCompose(repository::queryAsync)

结果是:

复制代码
CompletableFuture<Result>

这就是所谓的"展平"。

thenCompose 拆掉的是什么?

它拆掉的不是前面传过来的 CompletableFuture<String>

前面的 CompletableFuture<String> 完成后,里面的 String 会被拿出来传给 queryAsync

thenCompose 拆掉的是 queryAsync 返回的那一层:

复制代码
CompletableFuture<Result>

避免最终变成:

复制代码
CompletableFuture<CompletableFuture<Result>>

如果 thenCompose 里放普通函数呢?

普通函数比如:

复制代码
String f(String s) {
    return s.toUpperCase();
}

这种应该用:

复制代码
.thenApply(this::f)

因为它返回的是普通值 String

如果硬要用 thenCompose,就必须手动包一层:

复制代码
.thenCompose(s -> CompletableFuture.completedFuture(f(s)))

但这样没必要,反而更绕。

总结

记住这三句话就够了:

复制代码
返回普通值,用 thenApply
返回 CompletableFuture,用 thenCompose
只消费结果、不继续返回值,用 thenAccept

最终对比:

复制代码
.thenApply(body -> query(body))
// T -> U

.thenCompose(body -> queryAsync(body))
// T -> CompletableFuture<U>

所以,thenApply 不是不能接异步函数,而是接了之后会变成嵌套的 CompletableFuture

thenCompose 的意义就是把异步函数返回的 future 接到当前链上,让结果保持单层。

相关推荐
caoerzhong6 分钟前
JeeWMS 开源仓库管理系统全景解读:一套 Java WMS 如何把 WMS/OMS/BMS/TMS 装进同一个系统
java·开源
曹牧16 分钟前
Java集合框架:List、ArrayList、Map 和 HashMap
java
顾城猿21 分钟前
STOMP 协议
java
郑州光合科技余经理43 分钟前
同城外卖小程序开发:下单成功后,后台导出能不能对上用户端状态
开发语言·前端·git·后端·uni-app·php·ai编程
知识分享小能手1 小时前
C++ 学习教程,从入门到精通,C++ 入门知识 — 完整知识点(1)
开发语言·c++·学习
+VX:Fegn08951 小时前
计算机毕业设计|基于springboot + vue旅游管理系统(源码+数据库+文档)
java·开发语言·vue.js·spring boot·课程设计
需要8262 小时前
MySQL 索引 B+ 树与“查询为什么变慢了
java·spring boot·spring·spring cloud·tomcat
一 乐2 小时前
二手交易平台|基于springboot + vue二手交易平台(源码+数据库+文档)
java·数据库·vue.js·spring boot·小程序
万年咸鱼2 小时前
编译Java源程序
java
Wang's Blog2 小时前
Java 项目实战: 外卖平台-删除分类的关联校验与自定义业务异常
java·服务器·项目开发