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.
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();