Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Optional evaluation side Effects

Tags:

java

lambda

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?

like image 377
Dennis Ich Avatar asked Apr 26 '16 09:04

Dennis Ich


2 Answers

It is evaluated, because you are passing value of second() by value, rather than just passing a reference to function itself. You need to do one of following

System.out.println("GOT STRING: " + first().orElseGet(() ->second()));
System.out.println("GOT STRING: " + first().orElseGet(this::second));

To delay the evaluation till it is really needed.

like image 68
Artur Biesiadowski Avatar answered Sep 20 '22 13:09

Artur Biesiadowski


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.

like image 35
Jon Skeet Avatar answered Sep 20 '22 13:09

Jon Skeet