How to simplify a block of repeat code using java 8 functions

In my code, I have a section that tries to connect to some external interface, if it fails, then it will repeat it a fixed number of times. The code works, but is a little ugly. I am wondering if this can be done in a more elegant way using some of the fancy Java8 features?

int count = 0; final int maxRetries = 3; while ( count < maxRetries ) { try { // Some Code // break out of loop on success } catch ( final ExecutionException e ) { LOG.debug( "retrying..." ); if ( ++count >= maxRetries ) { LOG.debug( "do something else..."); //do something else } } } 
+6
source share
4 answers

What you can do is highlight the retry logic. You will need some auxiliary forests:

 interface ThrowingTask { void run() throws ExecutionException; } 

Now you write:

 boolean runWithRetries(int maxRetries, ThrowingTask t) { int count = 0; while (count < maxRetries) { try { t.run(); return true; } catch (ExecutionException e) { if (++count >= maxRetries) return false; } } } 

Now you can run things with repetitions without associating task logic with repeat logic:

 runWithRetries(MAX_RETRIES, () -> { /* do stuff */ }); 

You can configure this, since you like to accept lambdas that are called on restart, return a retry counter, etc. But the game is to write methods like runWithRetries that capture the control flow, but abstract about what kind of behavior you need to do this, so you only need to write a repeat loop once and then fill in the actual behavior you want , when it is necessary.

+15
source

Well, a more functional approach, in my opinion, will be to use the Try monad, which, unfortunately, is not for us in jdk 8 :(

However, you can still use the best monad library that provides it. With this, you can come up with some implementation as follows:

 public static <Out> Try<Out> tryTimes(int times, TrySupplier<Out> attempt) { Supplier<Try<Out>> tryAttempt = () -> Try.ofFailable(attempt::get); return IntStream.range(1, times) .mapToObj(i -> tryAttempt) .reduce(tryAttempt, (acc, current) -> () -> acc.get().recoverWith(error -> current.get())) .get(); } 

In short, this function simply calls tryAttempt calls, and if it fails, it attempts to recoverWith call the next tryAttempt call. The client code will look like this:

 tryTimes(10, () -> { // all the logic to do your possibly failing stuff } ); 

As a result, the client code will receive Try<T> , which can be unpacked by a direct call to .get() (if successful, returns a value, in case of failure, generates the main exception) or with other methods described in the library documentation.

Hope this helps.

UPDATE:

This can also be done in a functional way, using filter , findFirst and limit and without any external libraries:

 interface ThrowingSupplier<Out> { Out supply() throws Exception; } public static <Out> Optional<Out> tryTimes(int times, ThrowingSupplier<Out> attempt) { Supplier<Optional<Out>> catchingSupplier = () -> { try { return Optional.ofNullable(attempt.supply()); } catch (Exception e) { return Optional.empty(); } }; return Stream.iterate(catchingSupplier, i -> i) .limit(times) .map(Supplier::get) .filter(Optional::isPresent) .findFirst() .flatMap(Function.identity()); } 

Client code remains unchanged. Also note that it will not evaluate the expression times times, but will stop on the first successful attempt.

+4
source

Using Failsafe :

 RetryPolicy retryPolicy = new RetryPolicy() .retryOn(ExecutionException.class) .withMaxRetries(3); Failsafe.with(retryPolicy) .onRetry(r -> LOG.debug("retrying...")) .withFallback(e -> LOG.debug("do something else...")) .run(() -> someCode()); 

It is about as simple and expressive as you can get for your use case.

+3
source

Like some of the suggested approaches, you can create a class that separates the replay functionality from the rest of your code. The class below, which does just that, and also allows you to take input and return some result:

 public class Retrier<T, R> { private static final int DEFAULT_RETRY_COUNT = 3; private int retryCount; private Function<T, R> retriable; private T input; public Retrier(T input, Function<T, R> retriable) { this(input, retriable, DEFAULT_RETRY_COUNT); } public Retrier(T input, Function<T, R> retriable, int retryCount) { this.retryCount = retryCount; this.retriable = retriable; this.input = input; } public R execute() { int count = 0; while(true) { try { return retriable.apply(input); } catch (Exception e) { if (++count >= retryCount) { throw e; } } } } } 

And then you can use it, for example, for example,

 String result = new Retrier<String, String>("input", input -> {/* process the input and return the result*/}).execute(); 

And wherever your code throws an exception, it will be repeated.

+1
source

All Articles