javajava-stream

Map two Java `Optional`s or produce the one with a value


Is there a Java method or a succinct approach to combine two Optional instances using a mapping function if both are present; otherwise produce one or the other that has a value?

Here is how it would look conceptually in a clunky, non-functional approach:

<T> Optional<T> mapOr(Optional<T> optional1, Optional<T> optional2, BinaryOperator<T> fun)
  if(optional1.isPresent() && optional2.isPresent()) {
    return Optional.of(fun.apply(optional.get(), optional2.get()));
  } else {
    return optional1.or(() -> optional2);
  }
}

(I would never want to write code like that; I'm just spelling out the semantics.)

As a hypothetical use case, let's say I have two optional things I want to link together (like in a linked list), but I don't know if either exists. I want to end up with the first thing, optionally linking to the second one, through some hypothetical this.link(…) function.

Optional<E> linkedList = mapOr(optionalElement1, optionalElement2, this::link);

A more functional implementation approach might be:

<T> Optional<T> mapOr(Optional<T> optional1, Optional<T> optional2, BinaryOperator<T> fun) {
  return optional1.map(o1 -> optional2.map(o2 -> fun.apply(o1, o2))
      .or(() -> optional1)).or(() -> optional2);
}

But that is hideous.

Is there some common functional paradigm I'm missing here? It almost reminds me of a stream reduction operation, that is something like this:

<T> Optional<T> mapOr(Optional<T> optional1, Optional<T> optional2, BinaryOperator<T> fun) {
  return Stream.of(optional1, optional2)
      .filter(Optional::isPresent)
      .map(Optional::get)
      .reduce(fun);
}

What's cool about that approach is that it can be generalized to any number of Optionals. (I'm skeptical about its performance, though.) Still I would have to create a utility method—I wouldn't want to use something that wordy over and over (and I need to use this in several places). Later I realized I could convert the Optionals to streams, concat them, and then perform the reduce (as Holger mentions in one of the answers); that would be slightly prettier functional programming, but still pretty verbose.

Unfortunately after more thought, I realize that this is not exactly what I need; rather than link(link(link(1, 2), 3), 4), I need link(1, link(2, link(3, 4))). Further research reveals that what I want is a "right fold". I don't immediately see a right fold stream operation in Java. Nevertheless this would work with only two values.

Does Java have something more concise made for this operation already?


Solution

  • All of your approaches can be slightly simplified.

    1. The non-functional approach

      <T> Optional<T> mapOr(Optional<T> value1, Optional<T> value2, BinaryOperator<T> fun) {
          return value1.isPresent()? value2.isPresent()?
              Optional.of(fun.apply(value1.get(), value2.get())): value1: value2;
      }
      
    2. The first functional approach

      <T> Optional<T> mapOr(Optional<T> value1, Optional<T> value2, BinaryOperator<T> fun) {
          return value1.map(t1 -> value2.map(t2 -> fun.apply(t1, t2)).orElse(t1))
                       .or(() -> value2);
      }
      
    3. The Stream approach

      <T> Optional<T> mapOr(Optional<T> value1, Optional<T> value2, BinaryOperator<T> fun) {
          return Stream.concat(value1.stream(), value2.stream()).reduce(fun);
      }
      

    Considering that Java has no built-in operators we could utilize here, these are already the most concise ways to express this operation.