5

Assume I have this method

public Optional<String> validateAndReturnErrorMessage(SomeEcommerceSale s) {
    Optional<String> errorWithItem = validateItem(s.getItem());
    if (errorWithItem.isPresent()) return errorWithItem;

    Optional<String> errorWithPayment = validatePayment(s.getPayment());
    if (errorWithPayment.isPresent()) return errorWithPayment;
    
    // assume I have several other cases like the 2 above

    return Optional.empty(); // meaning there were no errors

My problem is, since OrElse and OrElseGet return <T> and not Optional<T>, is there a native way to do rewrite this into a more functional way without having several loosely coupled return statements?

EDIT

I would like to check validations one by one, since they are external and heavy. This way, I would call only the necessary ones

rado
  • 5,720
  • 5
  • 29
  • 51

3 Answers3

6

This kind of situation is what Optional.or was added for. It was added in Java 9, so if you're still on Java 8 then you can't use it.

return validateItem(s.getItem())
    .or(() -> validatePayment(req.getPayment()))
    .or(() -> /*another one*/);

If you are stuck on Java 8, you could write a helper method

public static <T> Optional<T> firstNonEmpty(List<Supplier<Optional<T>>> supplierList) {
    for (Supplier<Optional<T>> supplier : supplierList) {
        Optional<T> value = supplier.get();
        if (value.isPresent()) return value;
    }
    return Optional.empty();
}

And it use it like so:

return firstNonEmpty(
    Arrays.asList(
        () -> validateItem(s.getItem()),
        () -> validatePayment(req.getPayment()),
        () -> /*another one*/
    )
);
Michael
  • 41,989
  • 11
  • 82
  • 128
4

Just use or:

If a value is present, returns an Optional describing the value, otherwise returns an Optional produced by the supplying function.

return validatePayment(s.getItem()).or(() -> validatePayment(s.getPayment()));
M A
  • 71,713
  • 13
  • 134
  • 174
2

If you have a long/dynamic list of items to validate, you may want to use a stream:

return Stream.<Supplier<String>>of(s::getItem, req::getPayment, ...)
         .map(s -> this.validateItem(s.get()))
         .filter(Predicate.not(Optional::isEmpty)) //or filter(o -> !o.isEmpty())
         .findFirst();
ernest_k
  • 44,416
  • 5
  • 53
  • 99
  • on `.map(s -> this.validateItem(s.get()))` I would have to process a validation before checking if the previous one failed, this would not suit my needs, since these validations are external and heavy :( – rado May 05 '21 at 11:50
  • 1
    @rado no. `findFirst()` is short-circuiting. So if the first element passes all validation, it will be returned and the second one won't even be validated. – ernest_k May 05 '21 at 11:53
  • thanks for the clarification, this suit my needs – rado May 05 '21 at 11:55