How to call completable future in a loop and combine all the results?

Viewed 10388

I am trying to achieve something like this. This is a made up example that expresses the intent.

I want all the completable futures to execute and combine all their results to one result and return that. So for the example below, the collection allResults should have the strings "one", "two", "three", 3 times each. I want them all to run in parallel and not serially.

Any pointers to what API on the completable future I could use to achieve this, would be very helpful.

public class Main {


    public static void main(String[] args) {

        int x = 3;
        List<String> allResuts;

        for (int i = 0; i < x; i++) {

           //call getCompletableFutureResult() and combine all the results
        }

    }

    public static CompletableFuture<List<String>> getCompletableFutureResult() {

        return CompletableFuture.supplyAsync(() -> getResult());
    }

    private static List<String> getResult() {


        List<String> list = new ArrayList<>();
        list.add("one");
        list.add("two");
        list.add("three");

        return list;
    }


}
3 Answers

You can simply use CompletableFuture#thenCombine method like:

CompletableFuture<List<String>> combinedResults = CompletableFuture.completedFuture(List.of());
for (int i = 0; i < x; i++) {
    combinedResults = combinedResults.thenCombine(getCompletableFutureResult(), Main::unionLists);
}

The solution may look like:

public class Main {

    public static void main(String[] args) {
        int x = 3;
        CompletableFuture<List<String>> combinedResults = CompletableFuture.completedFuture(List.of());
        for (int i = 0; i < x; i++) {
            combinedResults = combinedResults.thenCombine(getCompletableFutureResult(), Main::unionLists);
        }
        combinedResults.thenAccept(System.out::println);
    }

    private static CompletableFuture<List<String>> getCompletableFutureResult() {
        return CompletableFuture.supplyAsync(Main::getResult);
    }

    private static List<String> getResult() {
       return List.of("one", "two", "three");
    }

    public static <E> List<E> unionLists(final List<? extends E> list1, final List<? extends E> list2) {
        final ArrayList<E> result = new ArrayList<>(list1.size() + list2.size());
        result.addAll(list1);
        result.addAll(list2);
        return result;
    }
}

I have used a utility method to add two lists, you can also use org.apache.commons.collections4.ListUtils.union also.

You can't collect the results in the 1st for loop, as that would mean you are not even starting the other tasks while waiting for the results of the previous tasks.

So start collecting the results once all the tasks are started.

public static void main(String[] args) throws Exception
{
  int x = 3;

  Queue<CompletableFuture<List<String>>> cfs = new ArrayDeque<>(x);
  for (int i = 0; i < x; i++)
  {
    cfs.add(getCompletableFutureResult());
  }

  List<String> allResuts = new ArrayList<>();
  for (CompletableFuture<List<String>> cf : cfs)
    allResuts.addAll(cf.get());

  System.out.println(allResuts);
}
Related