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

相关推荐
用户3126874877202 小时前
ConcurrentHashMap 怎么保证线程安全?从分段锁到 CAS+synchronized
java
vipxieliang3 小时前
ValidX vs Apache Commons Validator:功能与性能对比
java·spring boot
SimonKing3 小时前
升级Spring Boot 4后,从 Jackson 2 到 3,到底有哪些变化
java·后端·程序员
吃饱了得干活3 小时前
一篇讲清楚Spring Boot:自动装配、启动器、过滤器、拦截器、设计模式
java·spring boot·后端
lhldsg4 小时前
AI零售系统实战指南:从架构设计到落地部署全解析
java·人工智能·小程序·uni-app·零售
Interview Aid1125 小时前
TikTok OA 四题分享|半小时内 AC,题目基本都是实现题
java·开发语言·算法·面试·职场和发展
mldong5 小时前
换了工作流引擎,前端一行代码没改
java·架构
CoderIsArt12 小时前
C#中UI 线程与 Dispatcher
开发语言·ui·c#
微尘寒风12 小时前
【Git】的安装和使用
java·git
y = xⁿ13 小时前
DeepSeek Harness 学习日记:关于Agent接口,工具调用的底层实现
android·java·学习