Well, more functional approach in my opinion will be to use Try
monad which unfortunately is not there for us in jdk 8 :(
Nevertheless you still can use better-monads library which provides it. Having that you can come up with some implementation like this:
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();
}
Long story short this function just chains calls of tryAttempt
and in case of failed attempt tries to recoverWith
the next call of tryAttempt
. Client code is going to look like this:
tryTimes(10, () -> {
// all the logic to do your possibly failing stuff
}
);
As a result client code is going to get Try<T>
which can be unpacked by direct call of .get()
(in case of success returns the value, in case of failure throws underlying exception) or with other methods described in library documentation.
Hope it helps.
UPDATE:
This can be done also in functional way using the 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());
}
The client code remains the same. Also, please note that it is not going to evaluate expression times
times, but will stop on the first successful attempt.