This week, I learned about a nifty "new" feature of Optional that I want to share in this post.
It’s available since Java 9, so its novelty is relative.
Let’s start with the following sequence to compute the total price of an order:
public BigDecimal getOrderPrice(Long orderId) {
List<OrderLine> lines = orderRepository.findByOrderId(orderId);
BigDecimal price = BigDecimal.ZERO; (1)
for (OrderLine line : lines) {
price = price.add(line.getPrice()); (2)
}
return price;
}
| 1 | Provide an accumulator variable for the price |
| 2 | Add each line’s price to the total price |
Nowadays, it’s probably more adequate to use streams instead of iterations. The following snippet is the equivalent to the previous one:
public BigDecimal getOrderPrice(Long orderId) {
List<OrderLine> lines = orderRepository.findByOrderId(orderId);
return lines.stream()
.map(OrderLine::getPrice)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
Let’s focus on the orderId variable:
it may be null.
The imperative way to handle null values is to check it at the beginning of the method - and eventually throw:
public BigDecimal getOrderPrice(Long orderId) {
if (orderId == null) {
throw new IllegalArgumentException("Order ID cannot be null");
}
List<OrderLine> lines = orderRepository.findByOrderId(orderId);
return lines.stream()
.map(OrderLine::getPrice)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
The functional way is to wrap the orderId in an Optional.
This is what the code looks like using Optional:
public BigDecimal getOrderPrice(Long orderId) {
return Optional.ofNullable(orderId) (1)
.map(orderRepository::findByOrderId) (2)
.flatMap(lines -> { (3)
BigDecimal sum = lines.stream()
.map(OrderLine::getPrice)
.reduce(BigDecimal.ZERO, BigDecimal::add);
return Optional.of(sum); (4)
}).orElse(BigDecimal.ZERO); (5)
}
| 1 | Wrap the orderId in an Optional |
| 2 | Find relevant order lines |
| 3 | Use flatMap() to get an Optional<BigDecimal>;
map() would get an Optional<Optional<BigDecimal>> |
| 4 | We need to wrap the result into an Optional to conform to the method signature |
| 5 | If the Optional doesn’t contain a value, the sum is 0 |
Optional makes the code less readable!
I believe that readability should trump code style every single time.
Fortunately, Optional offers a stream() method (since Java 9).
It allows to simplify the functional pipeline:
public BigDecimal getOrderPrice(Long orderId) {
return Optional.ofNullable(orderId)
.stream()
.map(orderRepository::findByOrderId)
.flatMap(Collection::stream)
.map(OrderLine::getPrice)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
Here’s the summary of the type at each line:
| Snippet | Type |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
Functional code doesn’t necessarily mean readable code. With the last changes, I believe it’s both.