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>()
Returns a new CompletableFuture that is asynchronously completed by a task running in the given executor with the value obtained by calling the given Supplier. Returns a new CompletionStage that, when this stage completes normally, is executed with this stage's result as the argument to the supplied action.
CompletableFuture implements Future and CompletionStage interfaces and provides a huge set of convenience methods for creating, chaining and combining multiple Futures. It also has a very comprehensive exception handling support.
The CompletableFuture, was introduced in Java 8, provides an easy way to write asynchronous, non-blocking and multi-threaded code.
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With