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)?
source to share
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.
source to share
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 originalOptional
. This is probably a field. -
Inside the method providing
Optional
, you make sure you don't exposeprovideDefaultValue
elsewhere (since you don't need them) and use a conditional expressionboolean
before you packageOptional
.return value == null ? Optional.of(provideDefaultValue()) : Optional.of(value);
... but this really defeats the goal
Optional
as you are still doing the checknull
.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 returnOptional
this ...value = computeValue(); if(null == value) { value = provideDefaultValue(); } return Optional.of(value);
... but again, seriously canceling the target
Optional
as you are doing checksnull
.
source to share
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.
source to share