Optional.stream()
- February 22, 2021
- 2914 Unique Views
- 2 min read
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;
}
- Provide an accumulator variable for the price
- 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
}
- Wrap the orderIdin anOptional
- Find relevant order lines
- Use flatMap()to get anOptional;map()would get anOptional<Optional>
- We need to wrap the result into an Optionalto conform to the method signature
- If the Optionaldoesn't contain a value, the sum is0
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 | 
|---|---|
| Optional.ofNullable(orderId) | Optional | 
| stream() | Stream | 
| map(orderRepository::findByOrderId) | Stream | 
| flatMap(Collection::stream) | Stream | 
| map(OrderLine::getPrice) | Stream | 
| reduce(BigDecimal.ZERO, BigDecimal::add) | BigDecimal | 
Functional code doesn't necessarily mean readable code. With the last changes, I believe it's both.
To go further:
Originally published at A Java Geek on February 19th 2021
Don’t Forget to Share This Post!
 
                                         
                                         
                                         
                 
                             
                             
                             
         
        
Comments (0)
No comments yet. Be the first.