Do we need Optional ifNotPresent for use in the middle of chain?

Viewed 410

There are so many questions and threads about Optional API but I didn't found any for my case.

For example, I need first check emptiness of an Optional variable, for logging purpose, and then check the value, if presents, with some predicate. Whatever check fails, I need to throw an exception.

Below is my real workaround for that

SomeValue value = someOptional.orElseThrow(() -> {
    log.debug("nothing here");
    return new NothingSpecialHereException();
});

if (!value.isSpecial()) {
    log.debug("something here, but not special");
    throw new NothingSpecialHereException();
}

When I was looking for an alternative solution for that, I tried something like that

SomeValue value = someOptional
    .filter(SomeValue::isSpecial)
    .orElseThrow(() -> {
        log.debug("nothing special here"); // but this case for both "no value" and "value is not special"
        return new NothingSpecialHereException();
    });

I know that there's no any built-in solution for this situation in Java, but seems like I missing something like:

SomeValue value = someOptional
   .ifNotPresent(() -> log.debug("nothing here")) // that method returns Optional for further invocatons
   .filter(val -> {
      if (!val.isSpecial()) {
          log.debug("something here, but not special");
          return false;
      }
      return true;
   })
   .orElseThrow(NothingSpecialHereException::new);

This is not the first time when I miss something like ifNotPresent or else* methods for use in the middle of pipe, not in the end. IMO sometimes that approach might be more readable, for example

optional
    .map(...)
    .filter(...)
    .ifEmpty(...) // do smth after filter, maybe even throw
    .map(...) // and continue processing

Maybe anyone encountered same problem? Or maybe I missed some better solution? Maybe there's a library that provides solutions for that?

3 Answers

The JDK Optional includes (since Java 9 only, which was a major oversight in Java 8) ifPresentOrElse, which could be used with a no-op first argument. Alternately, the Vavr library is a set of functional wrappers that is slightly more consistent than Optional and provides additional useful wrappers such as Try at the cost of having an extra dependency.

SomeValue value = Optional.ofNullable(someValue.isPresent()?       
                   someValue.filter(SomeValue::isSpecial)
                  .orElseThrow(NothingSpecialHereException::new)
                  :null)
                  .orElseThrow(NothingHereException::new) ;

If someValue is present then the value can be set to special value or throw exception NothingSpecialHereException else if someValue is not present it will give Optinal it will check if null (someValue is not present) Throw NothingHereException.

Exception "NothingSpecialHereException" thrown only if value is present and not special.

Exception "NothingHereException" thrown only if value is not present.

Thanks to comments! I've checked out Vavr Option and Guava Optional APIs.

Guava Optional is even less rich in methods than Java Optional, so this is not a solution.

What I've found is the Vavr onEmpty method of Option class, that executes Runnable if the Option value is empty.

SomeValue value = Option.ofOptional(someOptional)
    .onEmpty(() -> log.debug("nothing here"))
    .filter(val -> {
      if (!val.isSpecial()) {
          log.debug("something here, but not special");
          return false;
      }
      return true;
   })
   .getOrElseThrow(NothingHereException::new);

That example displays one of debug messages and throws exception only in one place. Looks a lot better.

However, Vavr is a big library with own collections, functional interfaces and monads like above. This is a big dependency for a project and it is hardly will be implemented in my project or any other long-time-existed project just for new fancy Optional replacement.

Plus, Vavr's Option does'nt have something like onEmptyThrow, so it's not so perfect =).

This thread is still open for suggestions, but now for Java 8 workaround I think this comment may be considered as answer.

UPDATE

Seems like I've found the best solution for problems like that. Few days ago I found lombok's @ExtensionMethod annotation that makes possible solutions like below

class Extensions {
    public static <T, E extends Throwable> Optional<T> ifNotPresentThrow(Optional<T> opt, Supplier<E> exceptionSupplier) throws E {
        if (!opt.isPresent()) {
            throw exceptionSupplier.get();
        }
        return opt;
    }

}

@ExtensionMethod(Extensions.class)
class Test {
    public static void main(String[] args) {
        // assume there is someNullable variable

        // instead of writing
        Extensions.ifNotPresentThrow(Optional.ofNullable(someNullable), NothingHereException::new)
                .map(...);

        // I can write
        Optional.ofNullable(someNullable)
                .ifNotPresentThrow(NothingHereException::new)
                .map(...); // now possible to continue if optional present
    }
}

With that Kotlin-like extension methods it's possible to add any method to Optional's API. Lombok will just transform second variant into first during compilation.

However, this feature is experimental (seems like forever) and has possible bugs and decompiled bytecode looks pretty dirty (with various unnecessary local variables, etc.)

Related