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 做了几件事:
-
等上一步的
CompletableFuture<String>完成 -
取出里面真正的
String -
把这个
String传给queryAsync -
queryAsync返回CompletableFuture<Result> -
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 接到当前链上,让结果保持单层。