Java Optional evaluation side Effects

I had some trouble with the evaluation of Java optionals. Consider the following test:

@Test
public void test() {
    System.out.println("GOT STRING: " + first().orElse(second()));
}

private Optional<String> first() {
    System.out.println("Evaluating first");
    return Optional.of("STRING OPTIONAL");
}

private String second() {
    System.out.println("Evaluating second");
    return "SECOND STRING";
}

My expectation is that since first() returns a non empty Optional, that second is not evaluated. But in fact the output is:

Evaluating first
Evaluating second
GOT STRING: STRING OPTIONAL

Trying the function used in orElse in a test:

@Test
public void test2() {
    System.out.println("GOT STRING: " + (first() != null ? "FIRST IS NOT NULL" : second()));
}

Output:

Evaluating first
GOT STRING: FIRST IS NOT NULL

Why is the second option evaluated? This seems to be bad?

Jon Skeet
people
quotationmark

Why is the second option evaluated?

Because you're calling the second() method, in order to provide the value as an argument to orElse(). The value of the argument will be ignored (because you already have a value) but that doesn't mean it doesn't have to be provided.

Basically, this code:

first().orElse(second())

is equivalent to:

Optional<String> tmp1 = first();
Optional<String> tmp2 = second();
Optional<String> result = tmp1.orElse(tmp2);

There's nothing optional-specific here - that's how argument evaluation always works in Java.

Note that this is a big difference between a library construct (orElse) and the condition operator language construct (?:) which will only evaluate either the second or third operand, but never both.

As Artur mentioned, if you want to defer the call to second(), you need to use orElseGet, where the argument you pass isn't the value to use if first doesn't have one, but the call to make in order to get the value.

people

See more on this question at Stackoverflow