📜 ⬆️ ⬇️

Guide to java.util.concurrent.CompletableFuture class methods

Appears in Java8 class CompletableFuture - a means for transferring information between parallel execution threads. In essence, it is a blocking queue, capable of transmitting only one reference value. Unlike a normal queue, it also passes an exception, if it occurred during the calculation of the transmitted value.

The class contains several dozen methods in which it is easy to get lost. This article classifies these methods in several ways, so that they are easy to navigate.

To warm up, we will introduce new interfaces from the java.util.Function package, which are used as parameter types in many methods.

//  ,   BiFunction<T, U,R> { R apply(T t, U u); } //  ,    BiConsumer<T,U> { void accept(T t, U u) } //  ,   Function<T, R> { R apply(T t); } //  ,    Consumer<T> { void accept(T t); } //  ,   Supplier<T> { T get(); } 

Recall also the good old Runnable:
 //  ,    Runnable { void run(); } 

These interfaces are functional, that is, the values ​​of this type can be specified as references to objects, and references to methods or lambda expressions.
')
As a means of data transmission, the CompletableFuture class has two sub-interfaces - for writing and for reading, which in turn are divided into direct (synchronous) and mediated (asynchronous). Only the sub-interface of direct reading ( java.util.concurrent.Future , existing since java 5) is programmatically highlighted, but for classification purposes it is useful to mentally select the others. In addition to this separation by sub-interfaces, I will also try to separate the basic methods and methods that implement particular cases.

For brevity, instead of “an object of type CompletableFuture” we will say “futures”. “This futures” means futures to which the described method is applied.

1. Direct recording interface


It is clear that there are two basic methods - write the value and write the exception:
 boolean complete(T value) boolean completeExceptionally(Throwable ex) 
with obvious semantics.

Other methods:

 boolean cancel(boolean mayInterruptIfRunning) 
equivalent to completeExceptionally(new CancellationException) . Introduced for compatibility with java.util.concurrent.Future.

 static <U> CompletableFuture<U> completedFuture(U value) 
equivalent to CompletableFuture res=new CompletableFuture(); res.complete(value) CompletableFuture res=new CompletableFuture(); res.complete(value) .

 void obtrudeValue(T value) void obtrudeException(Throwable ex) 
Forcibly overwrite the stored value. A surefire way to shoot yourself in the foot.

2. Direct reading interface


 boolean isDone() 
Checks whether the result has already been recorded in this futures.

 T get() 
Waits if the result is not yet recorded, and returns a value. If an exception was written, throws a ExecutionException.

Other methods:

 boolean isCancelled() 
checks whether an exception was written using the cancel () method.

 T join() 
Same as get (), but throws a CompletionException.

 T get(long timeout, TimeUnit unit) 
get() with timeout.

 T getNow(T valueIfAbsent) 
returns the result immediately. If the result has not yet been recorded, returns the value of the valueIfAbsent parameter.

 int getNumberOfDependents() 
approximate number of other CompletableFuture waiting to be filled.

3. Interface Recording


 static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) 
The task with the supplier function is started, and the result of the execution is written to the futures. The task is run on a standard thread pool.

 static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor) 
The same, but running on the thread pool specified by the executor parameter.

 static CompletableFuture<Void> runAsync(Runnable runnable) static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor) 
The same as supplyAsync , but the action of the Runnable type and, accordingly, the result will be of the Void type.

4. Mediated Reading Interface


Instructs to perform the specified action (reaction) immediately to fill this (and / or other) futures. The most extensive sub-interface. We classify its components in two ways:

a) the way to start the response to the filling: it is possible to start it synchronously as a method when filling the futures, or asynchronously as a task on the thread pool. In the case of an asynchronous start, methods are used with the Async suffix (in two versions, the start is on the general stream ForkJoinPool.commonPool() , or on the stream indicated by the additional parameter). Further only methods for synchronous start will be described.

b) the topology of the relationship between this futures and the response to its filling: linear, type “any“ and type ”all”.

- linear relationship: one futures supply one value per reaction

- “any” method - two or more futures are entered; the first (in time) result that appeared in one of the futures is transferred to the reaction; other results are ignored

- “all” method - two or more futures are entered; the results of all futures are accumulated and then transferred to the reaction.

4.1 Perform a reaction to populate this futures (linear relationship)

These methods have names that begin with the then prefix, have one parameter, the reaction, and return a new futures type CompletableFuture for accessing the result of the reaction execution. They differ in the type of reaction.

 <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn) 
The main method in which the reaction gets the value from the futures and the return value is transferred to the resulting futures.

 CompletableFuture<Void> thenAccept(Consumer<? super T> block) 
The reaction retrieves the value from the futures, but does not return the value, so
The resultant futures value is of type Void .

 CompletableFuture<Void> thenRun(Runnable action) 
The reaction does not receive or return a value.

Let compute1..compute4 be references to methods. A linear chain with the transfer of values ​​from step to step may look like this:
 supplyAsync(compute1) .thenApply(compute2) .thenApply(compute3) .thenAccept(compute4); 

equivalent to a simple call
 compute4(compute3(compute2(compute1()))); 

 <U> CompletableFuture<U> thenCompose(Function<? super T, CompletableFuture<U>> fn) 
Same as thenApply , but the reaction itself returns futures instead of the finished value. This may be necessary if you want to use a complex topology reaction.

4.2 Perform a reaction to fill out any of the many futures.

 static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs) 
Returns a new futures contract that is filled in when any of the futures cfs parameter is cfs . The result is the same as the result of the completed futures.

4.3 Perform a reaction to fill in any of the two futures.

The main method:

 <U> CompletableFuture<U> applyToEither(CompletableFuture<? extends T> other, Function<? super T,U> fn) 
Returns a new futures, which is filled when this futures or futures, transmitted by the parameter other . The result is the same as the result of the completed futures.

The method is equivalent to the expression:
 CompletableFuture.anyOf(this, other).thenApply(fn); 

The remaining two methods differ only in the type of reaction:

 CompletableFuture<Void> acceptEither(CompletableFuture<? extends T> other, Consumer<? super T> block) CompletableFuture<Void> runAfterEither(CompletableFuture<?> other, Runnable action) 

It is not clear why there were 3 methods to do * Either (9 with * Async options), when one would be enough:
 <T> CompletableFuture<T> either(CompletableFuture<? extends T> other) { return CompletableFuture.anyOf(this, other); } 

then all these methods could be expressed as:

 f1.applyToEither(other, fn) == f1.either(other).thenApply(fn); f1.applyToEitherAsync(other, fn) == f1.either(other).thenApplyAsync(fn); f1.applyToEitherAsync(other, fn, executor) == f1.either(other).thenApplyAsync(fn, executor); f1.acceptEither(other, block) == f1.either(other).thenAccept(other); f1.runAfterEither(other, action) == f1.either(other).thenRun(action); 

etc. In addition, the method either could be used in other combinations.

4.4 Perform a reaction to fill two futures

 <U,V> CompletableFuture<V> thenCombine(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn) 
The main method. It has two futures input, the results of which are accumulated and then transferred to the reaction, which is a function of two parameters.

Other methods differ in the type of reaction:

 <U> CompletableFuture<Void> thenAcceptBoth(CompletableFuture<? extends U> other, BiConsumer<? super T,? super U> block) 
reaction does not return value

 CompletableFuture<Void> runAfterBoth(CompletableFuture<?> other, Runnable action) 
the reaction takes no parameters and does not return a value

4.5 Perform a reaction to fill many futures.

 static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs) 
Returns a CompletableFuture ending on completion of all futures in the parameter list. The obvious disadvantage of this method is that the resulting futures do not transfer the values ​​obtained in the futures parameters, so that if they are needed, they must be transferred in some other way.

4.6. Interception of execution errors

If at some point the futures fails, the exception is passed down the futures chain. To react to an error and return to normal execution, you can use the exception interception method.

 CompletableFuture<T> exceptionally(Function<Throwable,? extends T> fn) 
If this futures failed, the resulting futures will end with the result produced by the function fn . If the futures finished normally, then the resulting futures will end normally with the same result.

 <U> CompletableFuture<U> handle(BiFunction<? super T,Throwable,? extends U> fn) 
In this method, the reaction is always invoked, regardless of whether the given futures fail normally or abnormally. If the futures ended normally with the result r , then the parameters (r, null) will be passed to the reaction, if it crashes with the exception of ex, the parameters (null, ex) will be passed to the reaction. The result of the reaction may be of a different type than the result of the futures.

The following example is taken from http://nurkiewicz.blogspot.ru/2013/05/java-8-definitive-guide-to.html :

 CompletableFuture<Integer> safe = future.handle((r, ex) -> { if (r != null) { return Integer.parseInt(r); } else { log.warn("Problem", ex); return -1; } }); 

Here, the future produces a String result or an error, the reaction translates the result into an integer, and in case of an error, it returns -1. Note that, in general, the test should start with if (ex!=null) , since r==null can be both at abnormal and normal termination, but in this example, the case of r==null treated as an error.

If there is an interest in the form of proposals to solve certain problems, there will be a continuation.

Source: https://habr.com/ru/post/213319/


All Articles