8

I'm new to the Java optionals but I see this code written by another developer and I don't get it:

String t = null;
Optional.ofNullable("notnull")
    .orElse(
        Optional.ofNullable(t).orElseThrow(() -> new Exception("MyException"))
    );

Why does this code throw the exception? Why does it even go to the "orElse" branch?

Is this because of some weird execution order? So the first optional's value is not set before the orElse branch is evaluated?

whitebrow
  • 2,015
  • 21
  • 24
breakline
  • 5,776
  • 8
  • 45
  • 84

7 Answers7

10

The orElse stream is never invoked but the method itself is executed. This means that the method parameter is passed to it as well. So the part Optional.ofNullable(t).orElseThrow(() -> new Exception("MyException")) is being called regardless of the value passed to the first Optional.ofNullable call.

If you don't want this to happen you will need to pass a Supplier like this:

String t = null;
Optional.ofNullable("notnull")
    .orElseGet(
        () -> Optional.ofNullable(t).orElseThrow(() -> new RuntimeException("MyException"))
    );

The supplier is only invoked when the orElseGet stream is invoked. Note that you'll need a RuntimeException instead of a checked exception in order to be able to break from the supplier.

whitebrow
  • 2,015
  • 21
  • 24
6

That's because the code inside orElse() will be always evaluated. In other words it will be executed even if you specify a non-empty Optional, so that's why the Exception will be thrown.

If you check the orElse() section of Java Optional – orElse() vs orElseGet() article, you can see that in their example, where it says:

We can easily infer that the parameter of orElse() is evaluated even when having a non-empty Optional.

cнŝdk
  • 31,391
  • 7
  • 56
  • 78
3

What you wrote is the same like this:

String t = null;
String myException = Optional.ofNullable(t).orElseThrow(() -> new Exception("MyException"));
Optional.ofNullable("notnull").orElse(myException);

Part orElse of Optional is evaluated before you know is your value null or not.If you want "Lazy" evaluation consider orElseGet method.

Spasoje Petronijević
  • 1,476
  • 3
  • 13
  • 27
3

I really wonder why this code has been written in this fashion. It seems that it needed to trigger an exception with an explicit NULL value added to the optional.

As already said when using orElse() instead of orElseGet() the method is evaluated no matter the value of T (e.g Optional<T>).

I would use for a better fashion and undertanding:

String value = "notnull"; // could be null
Optional.ofNullable(value)
    .orElseThrow(MyException::new);

In case the value is NULL the exception is triggered.

Note : you can use method references to invoke exceptions

Hassam Abdelillah
  • 2,246
  • 3
  • 16
  • 37
2

Issue is in order of execution. It is trying to compute value for .orElse(...) and throws MyException on the .orElseThrow.

In other words if t is not null execution flow is following:

1) compute value of

Optional.ofNullable(t).orElseThrow(() -> new Exception("MyException"))

2) use value from (1) in

Optional.ofNullable("notnull").orElse(...)
i.bondarenko
  • 3,442
  • 3
  • 11
  • 21
2

The result is expected because in Java before invoking a method that takes a parameter, the thing that the JVM does before is evaluating the parameter value.
And that is the parameter value of the .orElse() invocation :

Optional.ofNullable(t).orElseThrow(() -> new Exception("MyException"))

Since t refers to null, the exception throw is expected.

davidxxx
  • 125,838
  • 23
  • 214
  • 215
2

There are few things you should note about Optional:

  1. If it is known the value being wrapped is null or not, use Optional.of() or Optional.empty(). If not sure, (for example value is a variable you got from somewhere else) use Optional.ofNullable()

  2. There is an important difference between Optional.orElse() and Optional.orElseGet().

    1. orElse takes an already computed value, if an expression is provided, it is executed and evaluated right then and there. This is happening in the code in question. Hence this else variant should be used for already available values or primitives.

    2. orElseGet takes a Supplier function that is only run when the optional chain is evaluated and alternative value is requested. This should be used where the alternative value is expensive to produce or compute.

    // Fine
    Optional<String> name = Optional.of(someValue).orElse("defaultName");
    
    // But this:
    Optional<String> name = Optional.of(someValue).orElse(db.queryName());
    
    // Is better written as following, as it saves you from an expensive operation
    Optional<String> name = Optional.of(someValue).orElseGet(() -> db.queryName());
    
    
S.D.
  • 29,290
  • 3
  • 79
  • 130