Using Java8 Optional <T> in a functional way to update source with default

This is more of a functional programming question than Java 8, but this is what I am currently using.

I have an original object (could represent a repository or a session ... doesn't matter here) that has a method retrieveSomething()

that returns Optional<SomethingA>

. I have a method somewhere that returns Something

by calling retrieveSomething()

and providing a default value in case the option is empty, like this:

return source.retrieveSomething()
             .orElseGet(() -> provideDefaultValue());

      

Now I want to change this code so that in case the source does not already contain a value (so the optional was empty), the source is updated with the default provided.

Of course, I could easily do this inside the lambda expression code block:

return source.retrieveSomething()
             .orElseGet(() -> {
                     Something sth = provideDefaultValue()
                     source.putSomething(sth);
                     return sth;
                 });

      

But if I understand correctly, I shouldn't use functions that cause side effects. So what's the "correct" (functional) way to do this while keeping the advantage of use Optional

(in real code, I actually do an operation map

on it too, but that doesn't matter here)?

+3


source to share


3 answers


You can follow how Java does it with Map.computeIfAbsent ()

which takes a second parameter, which is a function to calculate and insert a record:

Thus, your code will become:



Something something = source.computeIfAbsent(sth, (k)->provideDefaultValue());

      

The advantage of using a lambda to calculate the default instead of just passing it in is that the lambda will only be evaluated if needed, if calculating the default is costly, you only have to pay it when you need it.

+5


source


Conceptually, you are using an optional template given the absence of a return value . This means that if your instance source

does not contain a value to use, you can choose a default value to use in its place.

It is not recommended to modify Optional

directly to indicate its value; this instance may be temporary and will be different on subsequent calls to retrieve it.

Since the function call really determines what is returned by this Optional

, the only approach you have if you really want to go down that route is to change the way that value is calculated. It really should be done from the function providing Optional

, but it can be done outside of it if needed.



Since there is not enough code structure here to actually write an example, I'll describe the steps:

  • Outside of the providing method, Optional

    you write the same closure as before, with the side effect of setting the value used to compute the original Optional

    . This is probably a field.

  • Inside the method providing Optional

    , you make sure you don't expose provideDefaultValue

    elsewhere (since you don't need them) and use a conditional expression boolean

    before you package Optional

    .

    return value == null ? Optional.of(provideDefaultValue()) : Optional.of(value);
    
          

    ... but this really defeats the goal Optional

    as you are still doing the check null

    .

    A slightly better approach to the above would be to compute value

    in such a way that it is either itself or the default, and return Optional

    this ...

    value = computeValue();
    if(null == value) {
        value = provideDefaultValue();
    }
    return Optional.of(value);
    
          

    ... but again, seriously canceling the target Optional

    as you are doing checks null

    .

0


source


The answer I came up with myself, which I'm not quite happy with, but can serve as an example of what I'm looking for:

I could implement something similar to a class Pair<V1,V2>

and then map Optional<Something>

to Pair<Something, Boolean>

where the value Boolean

indicates if it was the default:

Pair<Something, Boolean> somethingMaybeDefault = 
    source.retrieveSomething()
          .map(sth -> new Pair<Something, Boolean>(sth, false))
          .orElseGet(() -> new Pair<Something, Boolean>(provideDefaultValue(), true));

      

Then I would update if the boolean is false:

if (somethingMaybeDefault.value2()) {
    source.putSomething(somethingMaybeDefault.value1());
}

      

And finally, let's return the new value:

return somethingMaybeDefault.value1();

      

Of course this uses an imperative style to update, but at least the features remain clean. I'm not sure if this is the best answer though.

0


source







All Articles