You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为什么CompletableFuture的supplyAsync单独使用与搭配thenCompose返回类型不一致

类型嵌套的根本原因

CompletableFuture.supplyAsync() 的返回值类型由传入的 Supplier 函数的返回值决定,会自动给返回值套一层 CompletableFuture 包装:

  • 你第二个版本中,supplyAsync 传入的 Supplier 最终返回的是 CompletableFuture<List<Car>> 类型,所以外层的 supplyAsync 自然就会把它再包一层,得到你遇到的 CompletableFuture<CompletableFuture<List<Car>>> 嵌套类型。
  • thenCompose 的作用就是专门打平这种嵌套的 CompletableFuture,它的定位和 Stream API 中的 flatMap 完全一致:如果前一步异步计算的返回值本身还是一个异步计算,thenCompose 会自动把两层 CompletableFuture 拆成一层,所以第一个版本的返回类型是符合预期的。
符合你需求的实现方案

你要求先在主线程同步执行 fetchCars(),再异步执行多个 fetchRating(),完全不需要把 fetchCars 放到 supplyAsync 中,也不需要用到 thenCompose,直接按如下写法即可,返回类型天然就是 CompletableFuture<List<Car>>:

package comparison;

import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import java.util.function.Function;

import static comparison.FakeDatabase.fetchCars;
import static comparison.FakeDatabase.fetchRating;

public class AsynchronousExample {
  public static void main(String[] args) {
    long start = System.currentTimeMillis();

    // 主线程同步执行fetchCars,符合你的需求
    List<Car> cars = fetchCars();
    // 提前创建线程池复用,避免原有代码中每次循环都新建线程池的资源浪费问题
    ExecutorService executor = Executors.newFixedThreadPool(Math.min(cars.size(), 7));

    List<CompletableFuture<Car>> updatedCarFutures = cars.stream()
            .map(car -> CompletableFuture.supplyAsync(() -> {
              System.out.println("fetching id:\t" + car.id);
              car.rating = fetchRating(car.id);
              return car;
            }, executor))
            .collect(Collectors.toList());

    CompletableFuture<List<Car>> cfCars = CompletableFuture
            .allOf(updatedCarFutures.toArray(new CompletableFuture[0]))
            .thenApply(v -> updatedCarFutures.stream()
                    .map(CompletableFuture::join)
                    .collect(Collectors.toList()));

    cfCars.join().forEach(System.out::println);
    executor.shutdown();

    long end = System.currentTimeMillis();
    System.out.println("Took " + (end - start) + "ms");
  }
}
补充说明

如果你确实需要把 fetchCars 也放到异步线程执行,又不想显式写 thenCompose,可以在第二个版本的 supplyAsync 后加一行 .thenCompose(Function.identity()) 手动打平嵌套的Future,但这种写法不如直接用 thenCompose 清晰,更推荐使用第一个版本的写法。

内容的提问来源于stack exchange,提问作者Jason Rich Darmawan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.08 19:42:01