CompletableFuture |然后应用vs thenCompose

时间:2017-03-25 16:47:35

标签: java java-8 completable-future

我无法理解thenApply()和thenCompose()之间的区别。

那么,有人可以提供有效的用例吗?

来自Java文档:

thenApply(Function<? super T,? extends U> fn)
  

返回一个新的CompletionStage,当此阶段完成时   通常,以此阶段的结果作为参数执行   提供的功能。

thenCompose(Function<? super T,? extends CompletionStage<U>> fn)
  

返回一个新的CompletionStage,当此阶段完成时   通常,以此阶段作为所提供的参数执行   功能

我得到thenCompose的第二个参数扩展了thenApply没有的CompletionStage。

有人可以举例说明我必须使用thenApplythenCompose吗?

6 个答案:

答案 0 :(得分:115)

如果您有同步映射函数,则使用

thenApply

CompletableFuture<Integer> future = 
    CompletableFuture.supplyAsync(() -> 1)
                     .thenApply(x -> x+1);
如果您有异步映射函数(即返回thenCompose的函数),则使用

CompletableFuture。然后它将直接返回结果的未来,而不是嵌套的未来。

CompletableFuture<Integer> future = 
    CompletableFuture.supplyAsync(() -> 1)
                     .thenCompose(x -> CompletableFuture.supplyAsync(() -> x+1));

答案 1 :(得分:36)

Java 9中更新的Javadoc可能有助于更好地理解它:

thenApply

<U> CompletionStage<U> thenApply​(Function<? super T,? extends U> fn)

  

返回一个新的CompletionStage,当此阶段完成时   通常,以此阶段的结果作为参数执行   提供的功能。

     

此方法类似于Optional.mapStream.map

     

有关规则,请参阅CompletionStage文档   特殊的完成。

thenCompose

<U> CompletionStage<U> thenCompose​(Function<? super T,? extends CompletionStage<U>> fn)
  

返回使用相同内容完成的新CompletionStage   值为给定函数返回的CompletionStage

     

当此阶段正常完成时,将调用给定的函数   这个阶段的结果作为论证,返回另一个   CompletionStage。当那个阶段正常完成时,   此方法返回的CompletionStage已完成相同的操作   值。

     

为确保进度,所提供的功能必须最终安排   完成其结果。

     

此方法类似于Optional.flatMap和   Stream.flatMap

     

有关规则,请参阅CompletionStage文档   特殊的完成。

答案 2 :(得分:22)

我认为@Joe C发布的回答是误导性的。

让我试着用一个例子解释thenApplythenCompose之间的区别。

我们假设我们有两种方法:getUserInfo(int userId)getUserRating(UserInfo userInfo)

public CompletableFuture<UserInfo> userInfo = getUserInfo(userId)

public CompletableFuture<UserRating> getUserRating(UserInfo)

两种方法返回类型都是CompletableFuture

我们希望首先致电getUserInfo(),并在完成后,使用生成的getUserRating()致电UserInfo

完成getUserInfo()方法后,让我们同时尝试thenApplythenCompose。区别在于返回类型:

CompletableFuture<CompletableFuture<UserRating>> f =
    userInfo.thenApply(this::getUserRating);

CompletableFuture<UserRating> relevanceFuture =
    userInfo.thenCompose(this::getUserRating);

thenCompose()就像Scala's flatMap一样,可以展平嵌套的未来。

thenApply()按原样返回嵌套的期货,但是thenCompose()压扁了嵌套的CompletableFutures,因此更容易将更多方法调用链接到它。

答案 3 :(得分:7)

thenApply上调用

thenComposeCompletableFuture,并通过提供Function对结果进行一些处理。 thenApplythenCompose各自返回一个CompletableFuture作为自己的结果,因此您可以链接多个thenApplythenCompose,每个都有一个{{1 }}对最后一个Function的结果进行处理。

Function有时需要同步执行某些操作,然后返回结果,在这种情况下,应使用Function

thenApply

您还可以在此CompletableFuture.completedFuture(1) .thenApply((x)->x+1) // adding one to the result synchronously .thenApply((x)->System.println(x)); 中执行异步操作,并且您执行的异步操作应返回一个Function。链中的下一个CompletionStage对获取Function作为输入并不感兴趣,而是对该CompletionStage的结果感兴趣。因此,您应该使用CompletionStage

thenCompose

在Javascript中,// addOneAsync may be implemented by using another thread, or calling a remote method // CompletableFuture<Integer> addOneAsync(int input); CompletableFuture.completedFuture(1) .thenCompose((x)->addOneAsync(x)) // doing something asynchronous .thenApply((x)->System.println(x)); 可以接受一个函数,该函数可以返回值或值的Promise.then。在Java中,由于类型规则,必须对两个函数进行明确的类型化,即。 Promise(Function<? super T,? extends U> fn。 (或者,如果返回Function<? super T,? extends CompletionStage<U>> fn,Java必须做某种类型检查以做一些特殊的事情,但是他们选择了前者)最终结果是CompletionStage分两部分实现Promise.thenthenApply

如果您对thenCompose感到困惑,也可以阅读my answer

答案 4 :(得分:0)

thenCompose()更适合于链接CompletableFuture。

thenApply()更适合于Completable future的转换结果。

您可以使用两种技术来实现您的目标,但是一种方法比另一种更适合一种用例。

public CompletableFuture<Integer> process(Integer i) {
    CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(
            () -> new HeavyTask(i).execute());
    return completableFuture;
}

@SneakyThrows
public CompletableFuture<Integer> thenApplyVsThenCompose() {
    // each time calling thenApply() function returns CompletionState
    // so you will get nested Futures 
    // you can think about it like map() java optional
    CompletableFuture<Future<Integer>> cf1 = CompletableFuture.supplyAsync(
            () -> new HeavyTask().execute())
            .thenApply(i -> process(i));

    // to get result you will have to get nested get() calls
    Integer resultFromThenApply = cf1.get().get();

    // when calling thenCompose() nested Futures are flatten
    // you can think about it like flatMap() java optional
    CompletableFuture<Integer> cf2;
    cf2 = CompletableFuture.supplyAsync(
            () -> new HeavyTask().execute())
            .thenCompose(this::process);

    // you have to just call one get() since thenCompose was flatten
    Integer resultFromThenCompose = cf2.get();
    return null;
} 

其他可视化两者之间差异的问题

当您不知道必须应用多少时间thenapply()/ thenCompose()时(如果是递归方法),您将如何实施解决方案?

public void nested() throws ExecutionException, InterruptedException {
    CompletableFuture<Integer> completableFutureToCompose = CompletableFuture.completedFuture(1);
    for (int i = 0; i < 10; i++) {
        log.info("Composing");
        completableFutureToCompose = completableFutureToCompose.thenCompose(this::process);
    }
    completableFutureToCompose.get();

    // not achievable using then apply
    CompletableFuture<Integer> completableFutureToApply = CompletableFuture.completedFuture(1);
    for (int i = 0; i < 10; i++) {
        log.info("Applying");
        completableFutureToApply = completableFutureToApply.thenApply(this::process).get();
    }
    completableFutureToCompose.get();
}

public CompletableFuture<Integer> process(Integer i) {
    log.info("PROCESSING");
    CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(
            () -> new HeavyTask(i).execute());
    return completableFuture;
}
  • 使用合成工具首先创建收据,再将期货如何相互传递然后执行
  • 使用apply可以在每次应用调用之后执行逻辑

答案 5 :(得分:0)

JoeC 的答案是正确的,但我认为可以明确 thenCompose 目的的更好比较是 thenApply 之间的比较>然后申请!一次是将同步映射传递给它,一次是将异步映射传递给它。

如果传递给 thenApply 的映射返回一个 String(非未来,所以映射是同步的),那么它的结果将是 CompletableFuture<String> .现在类似地,当传递给它的映射返回一个 CompletableFuture>(一个未来,所以映射是异步的)时,thenApply 的结果是什么?最终结果将是 CompletableFuture<CompletableFuture<String>>,这是不必要的嵌套(未来的未来仍然是未来!)。在这里,我们可以使用 thenCompose 来“组合”(嵌套)多个异步任务,而不会在结果中嵌套未来。