I need to perform an add operation on two big decimals that are wrapped optionals:
Optional ordersTotal;
Optional newOrder;
<
Not sure if you'll consider it more elegant, but here's one alternative:
ordersTotal = Optional.of(ordersTotal.orElse(BigDecimal.ZERO).add(newOrder.orElse(BigDecimal.ZERO)));
Another, based on @user140547's suggestion:
ordersTotal = Stream.of(ordersTotal, newOrder)
.filter(Optional::isPresent)
.map(Optional::get)
.reduce(BigDecimal::add);
Note that the first version returns Optional.of(BigDecimal.ZERO)
even when both optionals are empty, whereas the second will return Optional.empty()
in such a case.
I think the suggested answers of using streams or chains of methods on optionals are very clever, but perhaps so clever as to be obscure. The OP has modeled this as ordersTotal += newOrder
with the exception that if both are empty, the result should be empty instead of zero. Maybe it would be reasonable to write the code so that it says that:
if (!ordersTotal.isPresent() && !newOrder.isPresent()) {
result = Optional.empty();
} else {
result = Optional.of(ordersTotal.orElse(ZERO).add(newOrder.orElse(ZERO)));
}
While this isn't the shortest, it clearly expresses exactly what the OP asked for.
Now I've assigned the computed value to result
but the OP actually wanted to assign it back to ordersTotal
. If we know both are empty, we can then skip the then-clause that assigns empty to ordersTotal
. Doing that, and then inverting the condition gives something simpler:
if (ordersTotal.isPresent() || newOrder.isPresent()) {
ordersTotal = Optional.of(ordersTotal.orElse(ZERO).add(newOrder.orElse(ZERO)));
}
Now, this tends to obscure the both-empty special case, which might not be a good idea. On the other hand, this says "add the values if either is non-empty" which might make a lot of sense for the application.
What is wrong is your requirement not your solution. An empty Optional is not zero but a missing value. You're basically asking that 5 + NaN is equal to 5. Optional's flatMap guides you to the happy path: 5 + Nan is Nan and this is exactly what flatMap does.
Note that your solution
ordersTotal=ordersTotal.flatMap(b -> Optional.of(b.add(newOrder.orElse(BigDecimal.ZERO))));
will produce an empty Optional
, if ordersTotal
is empty, even if newOrder
is not.
This could be fixed by changing it to
ordersTotal=ordersTotal
.map(b -> Optional.of(b.add(newOrder.orElse(BigDecimal.ZERO))))
.orElse(newOrder);
but I’d prefer
ordersTotal=ordersTotal
.map(b -> newOrder.map(b::add).orElse(b))
.map(Optional::of).orElse(newOrder);
You could use a stream of optionals. Then you can make a stream of bigdecimals, and then reduce those bigdecimals, or else return 0.
This has the advantage that you don't have to change the code if you want to do that more than two optionals.
(code can be added later if needed, currently I don't have access to a computer)
I know this is an old thread, but how about this?
orderTotal = !newOrder.isPresent()?
orderTotal :
newOrder.flatMap(v -> Optional.of(v.add(orderTotal.orElse(BigDecimal.ZERO));
My thinking behind this approach is like this:
Behind all the shinny Optional etc. the basic logic here is still
orderTotal += newOrder
Before the first newOrder exists orderTotal does not exist, which is represented by an empty Optional in the code.