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

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

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

复制代码
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 接到当前链上,让结果保持单层。

相关推荐
‎ദ്ദിᵔ.˛.ᵔ₎16 分钟前
c++11:fuction包装器
开发语言·c++
kimnoic19 分钟前
Python 爬虫的原理
开发语言·python
Fluxproxy23 分钟前
Python爬虫异步提速终极方案|线程池ThreadPoolExecutor实战,告别单线程慢速爬取
开发语言·爬虫·python
蜗牛互联网23 分钟前
Java标准HTTP调用多模态模型的票据抽取前置校验
java·人工智能·python·http
淡海水36 分钟前
02-07-原理篇-GC暂停时间的本质
开发语言·unity·c#·gc·暂停时间
工具派39 分钟前
Agent 不是玄学:用 Java 状态机思维理解 LangGraph
java·人工智能·python
十年Java程序媛1 小时前
String / StringBuilder / StringBuffer|别再乱用字符串拼接,底层原理一次讲透
java·spring boot·后端
傲世仙尊1 小时前
类和对象中-构造函数与析构函数把初始化清理自动化
java·运维·自动化
她的男孩1 小时前
权限通了、列表有数,一点分页就炸Forge Admin 用 COUNT(1) 扛住复杂待办
java·spring boot·mybatis
骑着蜗牛撵大象3271 小时前
豆包 Seed 驱动的记账智能体:从拍票抽取到月报生成的全链路落地记录
java·前端·javascript·结构化输出·豆包seed·多模态识别·记账系统