Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mapping, aggregating and composing totals using Java 8 Streams

I'm trying to recreate a process to create a list of objects that are an aggregation of another list of objects using Java 8 Streams.

for example, I have a class, described below, that is provided from a database call or similar

public class Order {

    private String orderNumber;        
    private String customerNumber;
    private String customerGroup;
    private Date deliveryDate;
    private double orderValue;
    private double orderQty;
}

Elsewhere in my application I have a class OrderTotal which represents and aggregation of Order grouping by customer number and group and summing the totals of orderValue and orderQty. (With an equals and hashcode on customerGroup and customerNumber)

public class OrderTotal {

    private String customerGroup;
    private String customerNumber;
    private double totalValue;
    private double totalQty;
}

The 'long hand' way we have achieved this prior to java 8 is as follows

public Collection<OrderTotal> getTotals(List<Order> orders) {
    ///map created for quick access to the order total for each order 
    Map<OrderTotal, OrderTotal> map = new HashMap<>();
    ///loop through all orders adding to the relevaent order total per iteration
    for (Order order : orders) {
        OrderTotal orderTotal = createFromOrder(order);
        {
            ///if the order total already exists in the map use that one, otherwise add it to the map.
            OrderTotal temp = map.get(orderTotal);
            if(temp == null){
                map.put(orderTotal, orderTotal);
            }else{
                orderTotal = temp;
            }
        }
        ///add the values to the total 
        aggregate(orderTotal, order);
    }        
    return map.values();
}

private OrderTotal createFromOrder(Order order) {
    OrderTotal orderTotal = new OrderTotal();
    orderTotal.setCustomerGroup(order.getCustomerGroup());
    orderTotal.setCustomerNumber(order.getCustomerNumber());
    return orderTotal;
}

private void aggregate(OrderTotal orderTotal, Order order){
    orderTotal.setTotalQty(orderTotal.getTotalQty() + order.getOrderQty());
    orderTotal.setTotalValue(orderTotal.getTotalValue() + order.getOrderValue());
}

Ive been looking at Collectors using a grouping by and reduction functions but they all seem focused on aggregating the order class rather than composing the totals in the OrderTotal class.

I'm looking for a tidy stream or collect function that removes all the bloat from this code.

like image 353
user2936416 Avatar asked May 16 '19 13:05

user2936416


People also ask

What are the aggregate operations in Java 8 streams?

Aggregate operations − Stream supports aggregate operations like filter, map, limit, reduce, find, match, and so on. Pipelining − Most of the stream operations return stream itself so that their result can be pipelined.

What would be a good way of describing streams in Java 8?

Introduced in Java 8, the Stream API is used to process collections of objects. A stream is a sequence of objects that supports various methods which can be pipelined to produce the desired result. A stream is not a data structure instead it takes input from the Collections, Arrays or I/O channels.

How many types of streams does Java 8 have?

Java 8 offers the possibility to create streams out of three primitive types: int, long and double. As Stream<T> is a generic interface, and there is no way to use primitives as a type parameter with generics, three new special interfaces were created: IntStream, LongStream, DoubleStream.

What are aggregate operations in Java?

They process elements from a stream: Aggregate operations process elements from a stream, not directly from a collection. Consequently, they are also called stream operations. They support behavior as parameters: You can specify lambda expressions as parameters for most aggregate operations.


1 Answers

You can use the toMap collector as follows:

Collection<OrderTotal> result = orders.stream()
            .map(o -> createFromOrder(o))
            .collect(toMap(Function.identity(),
                        Function.identity(),
                        (l, r) -> {
                            aggregate(l, r);
                            return l;
                        }))
                .values();

Note that this requires changing the aggregate method parameters to aggregate(OrderTotal orderTotal, OrderTotal order){ ... } i.e. both parameters are of type OrderTotal.

or you could remove the aggregate method entirely and perform the logic in the toMap:

Collection<OrderTotal> result = orders.stream()
            .map(o -> createFromOrder(o))
            .collect(toMap(Function.identity(),
                    Function.identity(),
                    (l, r) -> {
                        l.setTotalQty(l.getTotalQty() + r.getTotalQty());
                        l.setTotalValue(l.getTotalValue() + r.getTotalValue());
                        return l;
                    }))
            .values();
like image 112
Ousmane D. Avatar answered Sep 22 '22 02:09

Ousmane D.