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

相关推荐
Doraemomo1 小时前
数据结构-环形链表
java·数据结构·链表
萧瑟余晖3 小时前
Java深入解析篇十七之SpringCloud
java·开发语言
是未才3 小时前
从输入 URL 到页面返回:DNS、路由、TLS 与 HTTP 完整链路
java·后端·计算机网络
ttod_qzstudio3 小时前
Java 常用语法极简通关(五):类与对象——字段、方法、构造器、this 与 static
java·开发语言·python
萧瑟余晖4 小时前
Java深入解析篇十七之Spring Security
java·开发语言·spring
离陌在学C#7 小时前
C# 重载与重写:深入理解面向对象编程的核心概念
java·c#
洛阳泰山7 小时前
AI 应用层被 Python 卷成红海,为什么我偏要用 Java 造一个 RAG + 工作流引擎?
java·人工智能·后端
二十雨辰7 小时前
[Java]-Spring面试题
java·开发语言
老马历写记7 小时前
Maven POM 依赖管理总结
java·maven·system·pom·optional