Reputation: 1739
I have a code:
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);
Output:
20 40
Question:
new CompletableFuture<Integer>()
Upvotes: 9
Views: 2081
Reputation: 46255
The first thing to note is that the methods of CompletableFuture
(e.g. thenApply
, thenAccept
, etc.) return a new CompletableFuture
instance. This forms a sort of "chain" where each new stage is dependent on the stage it was created from—its parent stage. When a stage completes, normally or exceptionally, the result is pushed to all its dependent, non-completed* stages (the same stage can have multiple dependent stages).
* As you'll see below you can complete a stage even if its parent hasn't completed yet. If and when the parent stage completes the completed dependent stage(s) will not be invoked as it's already completed. The consequences of this are covered briefly in an answer by Holger to another question.
In your first example you have the following:
CompletableFuture<Integer> c1 = new CompletableFuture<Integer>()
.thenApply((data) -> data * 2);
c1.thenAccept(System.out::println);
c1.complete(20);
Here c1
is the stage resulting from thenApply
, not new CompletableFuture<Integer>()
. When you call c1.complete(20)
you are completing the thenApply
stage (normally) with the given value (20
). The call to complete
is equivalent to the Function
transforming the previous stage's result and returning 20
. Now that thenApply
is completed it pushes the value to thenAccept
which results in 20
being printed to the console.
In your second example you have the following:
CompletableFuture<Integer> c2 = new CompletableFuture<>();
c2.thenApply(data -> data * 2)
.thenAccept(System.out::println);
c2.complete(20);
Here c2
is the stage resulting from new CompletableFuture<>()
, which is the parent of the thenApply
stage. So now when you call c2.complete(20)
you are completing the root stage which pushes the value to thenApply
. The Function
then transforms the value by multiplying it by 2
and pushing that result to thenAccept
. This results in 40
being printed out to the console.
The reason you must repeat <Integer>
in your first example is because the compiler cannot infer the type of the first stage without it. The signature of thenApply
is:
<U> CompletableFuture<U> thenApply(Function<? super T, ? extends U>)
The T
is determined by the type of this CompletableFuture
(the one the method is invoked on). The U
is determined by the Function
and, to an extent, the left hand side of a variable assignment where applicable. This means when you use the diamond operator (<>
) you are effectively using the following:
CompletableFuture<Integer> c = new CompletableFuture<Object>()
.thenApply(data -> data * 2);
// same as...
CompletableFuture<Integer> c = new CompletableFuture<>()
.thenApply(data -> data * 2);
Since all the compiler knows about the type of data
is that it's an Object
the multiplication is invalid; an Object
cannot be multiplied by 2
. Note that the above would be valid if you simply changed the Function
from data -> data * 2
to data -> 2
(but obviously those two functions aren't equivalent). This is because the left hand side of the assignment is related to the result of thenApply
, not new CompletableFuture<>()
.
When you explicitly specify <Integer>
the compiler then knows that the input type (T
) of the thenApply
stage is Integer
, which means it knows data
is an Integer
; an Integer
can be multiplied by 2
.
Upvotes: 13