Java 8 Complete allOf Futures of different data types

I have 3 CompletedFutures all 3 returning different data types.

I am looking to create a result object that is a composition of the result returned by all three futures.

So my current working code looks like this:

public ClassD getResultClassD() { ClassD resultClass = new ClassD(); CompletableFuture<ClassA> classAFuture = CompletableFuture.supplyAsync(() -> service.getClassA() ); CompletableFuture<ClassB> classBFuture = CompletableFuture.supplyAsync(() -> service.getClassB() ); CompletableFuture<ClassC> classCFuture = CompletableFuture.supplyAsync(() -> service.getClassC() ); CompletableFuture.allOf(classAFuture, classBFuture, classCFuture) .thenAcceptAsync(it -> { ClassA classA = classAFuture.join(); if (classA != null) { resultClass.setClassA(classA); } ClassB classB = classBFuture.join(); if (classB != null) { resultClass.setClassB(classB); } ClassC classC = classCFuture.join(); if (classC != null) { resultClass.setClassC(classC); } }); return resultClass; } 

My questions:

  • My guess is that since I use allOf and thenAcceptAsync , this call will not block. Do I understand correctly?

  • Is this the right way to deal with multiple futures that return different types of results?

  • Is it right to build a ClassD object inside thenAcceptAsync ?

  • Is it appropriate to use the join or getNow in lambda thenAcceptAsync?
+7
source share
3 answers

Your attempt is going in the right direction, but not right. Your getResultClassD() method returns an already created object of type ClassD , onto which an arbitrary stream will call modification methods without notifying the caller of getResultClassD() . This can lead to race conditions if the modification methods themselves are not thread safe, otherwise the caller will not know when the ClassD instance ClassD really ready for use.

Correct solution:

 public CompletableFuture<ClassD> getResultClassD() { CompletableFuture<ClassA> classAFuture = CompletableFuture.supplyAsync(() -> service.getClassA() ); CompletableFuture<ClassB> classBFuture = CompletableFuture.supplyAsync(() -> service.getClassB() ); CompletableFuture<ClassC> classCFuture = CompletableFuture.supplyAsync(() -> service.getClassC() ); return CompletableFuture.allOf(classAFuture, classBFuture, classCFuture) .thenApplyAsync(dummy -> { ClassD resultClass = new ClassD(); ClassA classA = classAFuture.join(); if (classA != null) { resultClass.setClassA(classA); } ClassB classB = classBFuture.join(); if (classB != null) { resultClass.setClassB(classB); } ClassC classC = classCFuture.join(); if (classC != null) { resultClass.setClassC(classC); } return resultClass; }); } 

Now the calling getResultClassD() object can use the returned CompletableFuture to query the execution status or chain-dependent actions, or use join() to get the result after the operation completes.

To solve other issues, yes, this operation is asynchronous and using join() within lambda expressions is appropriate. join was precisely created because Future.get() declared to throw checked exceptions makes unnecessary use in these lambda expressions.

Note that null tests are useful if these service.getClassX() can actually return null . If one of the service calls fails with an exception, the entire operation (represented by CompletableFuture<ClassD> ) will complete exclusively.

+7
source

I followed the same path that @Holger did in his answer, but wrapped the Service calls in Optional, which leads to the creation of cleaner code at the thenApplyAsync stage

 CompletableFuture<Optional<ClassA>> classAFuture = CompletableFuture.supplyAsync(() -> Optional.ofNullable(service.getClassA()))); CompletableFuture<Optional<ClassB>> classBFuture = CompletableFuture.supplyAsync(() -> Optional.ofNullable(service.getClassB())); CompletableFuture<Optional<ClassC>> classCFuture = CompletableFuture.supplyAsync(() -> Optional.ofNullable(service.getClassC())); return CompletableFuture.allOf(classAFuture, classBFuture, classCFuture) .thenApplyAsync(dummy -> { ClassD resultClass = new ClassD(); classAFuture.join().ifPresent(resultClass::setClassA) classBFuture.join().ifPresent(resultClass::setClassB) classCFuture.join().ifPresent(resultClass::setClassC) return resultClass; }); 
+3
source

Another way to handle this is if you don't want to declare so many variables to use thenCombine or thenCombineAsync to bind your futures together.

 public CompletableFuture<ClassD> getResultClassD() { return CompletableFuture.supplyAsync(ClassD::new) .thenCombine(CompletableFuture.supplyAsync(service::getClassA), (d, a) -> { d.setClassA(a); return d; }) .thenCombine(CompletableFuture.supplyAsync(service::getClassB), (d, b) -> { d.setClassB(b); return d; }) .thenCombine(CompletableFuture.supplyAsync(service::getClassC), (d, c) -> { d.setClassC(c); return d; }); } 

Getters will still run asynchronously and the results are executed in order. This is basically a different syntax option to get the same result.

+1
source

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


All Articles