Google Guava’s concurrency classes provide some advantages over using Java’s default classes. Guava’s Futures offer the following benefits:
- You can add “listeners” to the result of a Future’s success or failure, instead of explicitly checking for the return of the Future.get(), handling exceptions and so on
- You can chain multiple asynchronous pieces of code together using provided utility functions rather than writing repetitive code for the same.
Below, we will see how to use the chaining feature. In the process, we will learn how “listeners” are used internally to implement the same.
What the code does is first create a UserId object, and then use it to obtain a UserDetails object. Both these operations are presumed to be long running operations in the real world, and hence implemented using Futures. I have added inline comments so hopefully it is all self-explanatory!
public class ListenableFutureExample1 {
public static void main(String[] args) throws Exception {
// Step 1. Create an instance of an executor that provides instances of ListenableFuture-s
final ListeningExecutorService executor =
MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(5));
// Step 2. Use that to create a Future that returns a userId object
ListenableFuture<UserId> userIdFuture = executor.submit(
new Callable<UserId>() {
public UserId call() {
// Initializing "1" as the user id
return new UserId(1);
}
}
);
// Step 3. Let's say you want to convert the userId to userDetails.
// And for that you have a function that may run for a long time and hence is better invoked
// asynchronously. Guava's AsyncFunction class is appropriate here
AsyncFunction<UserId, UserDetails> userDetails = new AsyncFunction<UserId, UserDetails>() {
public ListenableFuture<UserDetails> apply(UserId userId) {
return Futures.immediateFuture(new UserDetails(userId));
// The above instantiation of new UserDetails can be replaced with any long running
// piece of code
}
};
// Step 4. Chain the future and the function
// This works by registering a listener to the output of userIdFuture. This listener invokes the
// long running, asynchronous userDetails function above within the passed in executor
ListenableFuture<UserDetails> userDetailsFuture = Futures.transformAsync(userIdFuture,
userDetails, executor);
// Step 5. print the output ("some username")
System.out.println(userDetailsFuture.get().getUserName());
executor.shutdownNow();
}
// A simple holder of userId info
private static class UserId {
private long id;
public UserId(long id) {
this.id = id;
}
}
// Maps userId to username (currently a dummy implementation)
private static class UserDetails {
private final UserId userId;
public UserDetails(UserId userId) {
this.userId = userId;
}
public String getUserName() {
// This can be replaced by a lookup from userId to username
return "some username";
}
}
}
Within the Guava library, the following piece of code in AbstractTransformFuture registers the AsyncFunction above as a listener to the userId Future object:
AsyncTransformFuture<I, O> output = new AsyncTransformFuture<>(input, function);
input.addListener(output, rejectionPropagatingExecutor(executor, output));
In the code above, “input” is the userId Future, and “function” is the passed in AsyncFunction.
2 thoughts on “How is Google Guava ListenableFuture better than Java Future?”