6

So I'm working with Optionals and came across a strange behaviour. I want to know if this is really an intendet "feature" or something...odd...

Here is the given example: I've got a method with an Optional in whose orElse I want to evaluate an other optional. If the other Optional is not present, I'll raise an IllegalArgumentException:

firstOptionalVar.orElse(secondOptionalVar.orElseThrow(IllegalArgumentException::new));

Now if the secondOptionalVar is an empty Optional, it will raise an IllegalArgumentException, even if the firstOptionalVar is Present. This doesn't seem right to me. I would expect it to just raise an IllegalArgumentException if the firstOptionalVar would not be present.

It's not a big deal to get arround this behavior with java7-methods like:

firstOptionalVar.isPresent() ? firstOptionalVar.get() : secondOptionalVar.orElseThrow(IllegalArgumentException::new);

Has anyone else experienced this behaviour before? Is this really the way optionals should behave?

Numbernick
  • 170
  • 3
  • 10
  • `flatMap` does the opposite unfortunately. – biziclop Jul 25 '16 at 15:22
  • 2
    The anwer to the exception can be found in the linked question or the answers you got. Note that the "or" operation you want to perform will be available on Java 9: http://stackoverflow.com/questions/24599996/get-value-from-one-optional-or-another – Tunaki Jul 25 '16 at 15:27
  • Thank you for your answer. The solution of @resueman will work for me, but it's good to know how java9 will handle this case :) – Numbernick Jul 25 '16 at 15:53

2 Answers2

8

This is the intended behavior. orElse expects an argument of type T (whatever the generic type of the Optional is. orElseThrow returns a T, so it needs to be evaluated first, in order to pass the parameter into orElse.

What you want is orElseGet, which takes a Supplier<T>. That will delay execution of the orElseThrow until after firstOptionalVar has already been checked.

So your code should look like this:

firstOptionalVar.orElseGet(() -> secondOptionalVar.orElseThrow(IllegalArgumentException::new));

That will turn the orElseThrow section into a lambda, and only evaluate it if it's needed (ie. when firstOptionalVar doesn't have a value to get).

resueman
  • 10,572
  • 10
  • 31
  • 45
3

At the heart of everything, you're still only calling Java methods, and therefore the standard Java order of evaluation applies.

Specifically: secondOptionalVar.orElseThrow(IllegalArgumentException::new) has to be evaluated before the call to firstOptionalVar.orElse(), as it provides the argument of firstOptionalVar.orElse().

I can't see any easy way of resolving this with standard methods of Optional, you can always build a stream out of the two optionals and get the first element or else throw an exception, but it seems a bit convoluted.

biziclop
  • 48,926
  • 12
  • 77
  • 104