This is Part 1 of Future vs CompletableFuture. In this blog we will be comparing Java 5’s Future with Java 8’s CompletableFuture on the basis of two categories i.e. manual completion and attaching a callable method.
What is CompletableFuture?
CompletableFuture is used for asynchronous programming in Java. Asynchronous programming is a means of writing non-blocking code by running a task on a separate thread than the main application thread and notifying the main thread about its progress, completion or failure.
CompletableFuture implements two interfaces:
Future vs. CompletableFuture
1. Manual Completion
Talking about manual completition , future provides an isDone() method to check whether the computation is done or not, and a get() method to retrieve the result of the computation when it is done. But if there comes a scenario where you need to complete it manually, Future does not provides any means to do so.
But in Java 8’s completableFuture, CompletableFuture.complete() method helps us to manually complete a Future. Let’s have a look at an example to see it in more detail.
Creating a CompletableFuture simple by having a no-arg constructor.
To fetch the result, you can use get() method.
Since we know that get() method blocks until the future is complete, the above call will block forever since the future is never completed. Therefore, we can use complete() method in order to manually complete the result.
2. Attaching a callable method
While using Future, we do not get notified when it is complete neither does it provides us a callable method which will automatically be called when the result is available but CompletableFuture provides us with a lot of callable methods which can be used as per our use case. But before we go through callable methods let’s have a basic understanding of runAsync() and supplyAsync() for asynchronous computation.
It is used for running some background task asynchronously but not returning anything by using a Runnable instance. It takes a Runnable object and returns CompletableFuture. For example:
In the above example, we have used lambda expression for passing Runnable object.
This method is used when you want to return some value from the background task running asynchronously. It takes a Supplier and returns CompletableFuture. Here, Supplier is a functional interface and T is the type of value returned from the supplier. For example:
Note: Async methods are used to run the task on a separate thread apart from the main thread whereas the methods without the Async postfix run the execution stage using a calling thread. When using methods with Async postfix, you can specify the executor as second argument. By default it is ForkJoinPool.commonPool() method that is used.
Now, let’s have a look at each of the callable method:
It takes a Function as an argument. Function is a functional interface which represents a function that it takes argument of type T and returns argument of type R.
We can even apply a sequence of transformations using thenApply() where the result of 1st thenApply() is passed to the 2nd thenApply() and so on. For example:
It takes a Consumer and returns CompletableFuture. It has access to the result of the CompletableFuture on which it is attached. For example:
It also takes a Consumer and returns CompletableFuture. If you neither need the value of the computation nor want to return some value at the end of the chain, then you can pass a Runnable lambda to the thenRun() method. Hence, in this case we do not have access to future’s result.
- While processing results of asynchronous computations, we also have callable methods with async variants where we can specify the executor as discussed above.
- The consumer methods are often used as the last callback in the callback chain.
In the next blog, I will be comparing Future with CompletableFuture on the basis of
- Combining 2 CompletableFutures together
- Combining multiple CompletableFutures together
- Exception Handling