Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Optional's `orElse` lazy evaluation fail leads to performance hit

Tags:

java

optional

Consider the following use case of an application with a user preference system:

We want to get the bool value of the preference MyFlag.
In the best case we want it from the settings of the current user.
If that fails, we want to get MyFlag from the default settings.
If even that fails, throw.

The settings are on a server. This connection is slow and can fail.
Fetching the settings and getting the prefernce can all fail, too.
So lets use javas Optionals:

public static boolean getMyFlag()throws NoSuchElementException
{
        return getUserOrDefaultPreference("MY_FLAG");
}

private Boolean getUserOrDefaultPreference(String preferenceName) throws NoSuchElementException
{
    return Optional.ofNullable(getConnection())              // get slow connection
        .map(connection -> connection.getUserSettings())     // get user settings
        .map(settings -> settings.getPref(preferenceName))   // return preference
        .orElse(slowlyGetDefaultPreference(preferenceName)); // or else return default value
}

private Boolean slowlyGetDefaultPreference(String preferenceName) throws NoSuchElementException
{
    return Optional.ofNullable(getConnection())             // get slow connection
        .map(connection -> connection.getDefaultSettings()) // get default settings
        .map(settings -> settings.getPref(preferenceName))  // return preference
        .orElseThrow(() ->  new NoSuchElementException());  // if any of the above fails throw
}

Problem here is that the connection can be very slow. When .orElse(slowlyGet...); is called the func slowlyGetDefaultPreference() is evaluated first, regardless, if my optional is empty or has a value. This is a performance penalty, I have to avoid.

I tried using a supplier via .orElseGet(() -> slowlytGet..), but that resulted in the same problem.

So my only recourse is the ugly isPresent() anti-pattern, which from a readability standpoint ruins the whole Optional flow:

private Boolean getUserOrDefaultPreference(String preferenceName) throws NoSuchElementException
{
    Optional<Boolean> opt = Optional.ofNullable(getConnection())
        .map(connection -> connection.getUserSettings())  
        .map(settings -> settings.getPref(preferenceName));

    if(opt.isPresent())
    {
        return opt.get();
    }
    else
    {
        slowlyGetDefaultPreference(preferenceName));
    }
}

The same holds for throwing the exception, as I do not want to pay the cost of constructing one.

Am I missing something here or is this the only solution?

like image 293
Da Frenk Avatar asked Feb 08 '17 08:02

Da Frenk


People also ask

What is the difference between orElse and orElseGet?

orElse(): returns the value if present, otherwise returns other. orElseGet(): returns the value if present, otherwise invokes other and returns the result of its invocation.

How do you use orElse optional?

The orElse() method of java. util. Optional class in Java is used to get the value of this Optional instance, if present. If there is no value present in this Optional instance, then this method returns the specified value.

What is optional ofNullable?

What is the ofNullable() method of the Optional class? The ofNullable() method is used to get an instance of the Optional class with a specified value. If the value is null , then an empty Optional object is returned.


1 Answers

The difference between orElse() and orElseGet(lambdas) is, the first function is always called, but orElseGet is called, when some obejct before orElseGet in Optional.ofNullable() was null.

It means, if you don't want call every time method from orElse(), you must useorElseGet()

In your example, for your methodslowlyGetDefaultPreference(preferenceName) please use orElseGet()

return Optional.ofNullable(getConnection())              // get slow connection
    .map(connection -> connection.getUserSettings())     // get user settings
    .map(settings -> settings.getPref(preferenceName))   // return preference
    .orElseGet(() -> slowlyGetDefaultPreference(preferenceName));

Then you code not evaluate last method, when all object in map function is not null

like image 145
MatWdo Avatar answered Nov 10 '22 00:11

MatWdo