Google Guava : future chaining and transformation

In one of previous articles we saw that Google Guava provides a listenable futures which are able to invoke callback methods. But it's not theirs single feature.

In this article we'll see how to transform future tasks into one common result. The first part will explain this concept while the second one will show how to implement it.

Chaining and transforming tasks in Google Guava

Google Guava introduces listenable futures but it's not the only new feature. It also provides a way to transform results from future objects. The transformation is achieved thanks to transform method from com.google.common.util.concurrent.Futures class. To simplify the workflow, this method takes a ListenableFuture tasks in argument and return the final result after the computation of all tasks results.

The transformation can be useful when we must to divide some request to be able to get the result quicker. For example, imagine the situation when you need to make 2 separate request to get the object understandable by your application. And these two calls must be make one after another, in ordered way. This situation can be resolved with transform method which will return the final object after receiving the responses of the first request.

Example of transform in Google Guava

To see how transform works, there are a simple test case:

public class FuturesTest {

  @Test
  public void test() {
    long start = System.currentTimeMillis();
    String itemName = "black wheels";
    String itemName2 = "glass";
    ListeningExecutorService pool = MoreExecutors.sameThreadExecutor();
    ListenableFuture<String> firstResult = pool.submit(new ApiCaller(3000, itemName));
    ListenableFuture<String> secondResult = pool.submit(new ApiCaller(6000, itemName2));
    
    final Map<String, String> threads = new HashMap<String, String>();
    /**
      * Transform method can take the 3rd argument, an instance of Executor. If you want to execute given 
      * Function in the same thread as the caller thread, you can omit this argument. If you want
      * to use an executor, you have to specify it. 
      * Consider this case when you want to do not disturb the calling thread.
      */
    Function<String, CarItem> function1 = new Function<String, CarItem>() {
      @Override
      public CarItem apply(String item) {
        // imagine that you make to query your database and make some conversions before getting the expected CarItemObject
        threads.put("no-executor", Thread.currentThread().getName());
        return new CarItem(item);
      }
    };
    Function<String, CarItem> function2 = new Function<String, CarItem>() {
      @Override
      public CarItem apply(String item) {
        // imagine that you make to query your database and make some conversions before getting the expected CarItemObject
        threads.put("with-executor", Thread.currentThread().getName());
        return new CarItem(item);
      }
    };
    ListenableFuture<CarItem> car = Futures.transform(firstResult, function1);
    ListenableFuture<CarItem> carExecutor = Futures.transform(secondResult, function2, Executors.newCachedThreadPool());
    
    long end = System.currentTimeMillis();
    
    int executionTime = Math.round((end - start)/1000);
    assertTrue("Execution time should be 9 seconds (first Future executed within 6 seconds, the second within 3 seconds) but is "+executionTime + " seconds", executionTime == 9);
    
    assertTrue("Car item name should be '"+itemName+"' but was '"+car.get().getName()+"'", 
      car.get().getName().equals(itemName));

    assertTrue("Car item name should be '"+itemName2+"' but was '"+carExecutor.get().getName()+"'", 
      carExecutor.get().getName().equals(itemName2));

    String curThreadName = Thread.currentThread().getName();
    assertTrue("Transformation without Executor should be made in calling thread ("+Thread.currentThread().getName()+")", 
      curThreadName.equals(threads.get("no-executor")));
    assertTrue("Transformation with Executor should be made in thread pool but it wasn't", 
      !threads.get("with-executor").equals(curThreadName));
  }
}

class CarItem {
  private String name;
  
  public CarItem(String name) {
    this.name = name;
  }
  
  public String getName() {
    return this.name;
  } 
}

class ApiCaller implements Callable<String> {
  private int timeSleep;
  private String result;
  
  public ApiCaller(int timeSleep, String result) {
    this.timeSleep = timeSleep;
    this.result = result;
  }

  @Override
  public String call() throws Exception {
    Thread.sleep(this.timeSleep);
    return this.result;
  }
}

In this article we saw how to transform the computation of two future tasks into one result. This feature can be useful when you have to deal with two future tasks where the final result can be calculated only when the first task is executed before the second.


If you liked it, you should read:

📚 Newsletter Get new posts, recommended reading and other exclusive information every week. SPAM free - no 3rd party ads, only the information about waitingforcode!