可完成的未来 - 完整的方法

Completable future - complete method

我有一个代码:

CompletableFuture<Integer> c1 = new CompletableFuture<Integer>()
        .thenApply((data) -> data * 2);
c1.thenAccept(System.out::println);

c1.complete(20);


CompletableFuture<Integer> c2 = new CompletableFuture<>();

c2.thenApply(data -> data * 2)
        .thenAccept(System.out::println);
c2.complete(20);

输出:

20 40

问题:

  1. 为什么c1和c2的输出不同?
  2. 为什么需要通过调用在 c1 中重复 future 类型:

new CompletableFuture<Integer>()

首先要注意的是CompletableFuture的方法(如thenApplythenAccept等)return一个new CompletableFuture 实例。这形成了一种 "chain",其中每个新阶段都依赖于创建它的阶段——它的父阶段。当一个阶段正常或异常完成时,结果将被推送到其所有依赖的 未完成* 阶段(同一阶段可以有多个依赖阶段)。


* 正如您将在下面看到的,即使其父级尚未完成,您也可以完成一个阶段。如果父阶段完成并且当父阶段完成时,将不会调用 completed 依赖阶段,因为它已经完成。 对另一个问题的 简要介绍了这种情况的后果。


问题 1

在您的第一个示例中,您有以下内容:

CompletableFuture<Integer> c1 = new CompletableFuture<Integer>()
        .thenApply((data) -> data * 2);
c1.thenAccept(System.out::println);
c1.complete(20);

这里 c1 是由 thenApply 而不是 new CompletableFuture<Integer>() 产生的阶段。当您调用 c1.complete(20) 时,您正在 完成 thenApply 阶段(通常)具有给定值 (20)。 complete 的调用相当于 Function 转换前一阶段的结果并 returning 20。现在 thenApply 已完成,它将值推送到 thenAccept,这导致 20 被打印到控制台。

在您的第二个示例中,您有以下内容:

CompletableFuture<Integer> c2 = new CompletableFuture<>();
c2.thenApply(data -> data * 2)
        .thenAccept(System.out::println);
c2.complete(20);

此处 c2 是由 new CompletableFuture<>() 产生的阶段,它是 thenApply 阶段的父级。所以现在当您调用 c2.complete(20) 时,您正在完成 root 阶段,该阶段将值推送到 thenApplyFunction 然后通过将值乘以 2 并将结果推送到 thenAccept 来转换该值。这导致 40 被打印到控制台。


问题 2

您必须在第一个示例中重复 <Integer> 的原因是因为如果没有它,编译器将无法推断出第一阶段的类型。 thenApply的签名是:

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

T 由此 CompletableFuture 的类型决定(调用方法的那个)。 UFunction 决定,并且在一定程度上由变量赋值的左侧决定。这意味着当您使用菱形运算符 (<>) 时,您实际上是在使用以下内容:

CompletableFuture<Integer> c = new CompletableFuture<Object>()
        .thenApply(data -> data * 2);

// same as...
CompletableFuture<Integer> c = new CompletableFuture<>()
        .thenApply(data -> data * 2);

因为所有编译器都知道 data 的类型是 Object 乘法是无效的; Object 不能乘以 2。请注意,如果您只是将 Functiondata -> data * 2 更改为 data -> 2,则上述内容将有效(但显然这两个功能并不等效)。这是因为赋值的左侧与 thenApply 的结果相关,而不是 new CompletableFuture<>().

当您显式指定 <Integer> 时,编译器就会知道 thenApply 阶段的输入类型 (T) 是 Integer,这意味着它知道 data 是一个 IntegerInteger 可以乘以2