I have a problem with streams. I have many Customer
objects and I would like to calculate which one of them paid the most.
This is my sample data:
class Orders {
private List<Order> orders = new ArrayList<>();
public void prepareData() {
Product socks = new ProductBuilder()
.setPrice(new BigDecimal("23"))
.setCategory(Category.C)
.setName("SOCKS")
.build();
Product jacket = new ProductBuilder()
.setPrice(new BigDecimal("199"))
.setCategory(Category.A)
.setName("JACKET")
.build();
Product watch = new ProductBuilder()
.setPrice(new BigDecimal("100"))
.setCategory(Category.B)
.setName("WATCH CASIO")
.build();
Customer john = new CustomerBuilder()
.setAge(18)
.setName("JOHN")
.setSurname("JOHNSON")
.setEmail("[email protected]")
.build();
Customer mike = new CustomerBuilder()
.setAge(20)
.setName("MIKE")
.setSurname("MAX")
.setEmail("[email protected]")
.build();
Order orderJohn = new OrderBuilder()
.setQuantity(2)
.setCustomer(john)
.setProduct(watch)
.setOrderDate(LocalDate.now())
.build();
Order orderJohn2 = new OrderBuilder()
.setQuantity(4)
.setCustomer(john)
.setProduct(socks)
.setOrderDate(LocalDate.now())
.build();
Order orderMike = new OrderBuilder()
.setQuantity(2)
.setCustomer(mike)
.setProduct(jacket)
.setOrderDate(LocalDate.now())
.build();
orders.add(orderJohn);
orders.add(orderJohn2);
orders.add(orderMike);
}
}
Now I should group by customer because one customer has many orders and calculate price * and quantity and select maximum using orders.stream()
? How can I do that?
My class definitions:
public class Order {
private Customer customer;
private Product product;
private int quantity;
private LocalDate orderDate;
//get/set
}
public class Customer {
private String name;
private String surname;
private int age;
private String email;
//get/set
}
public class Product {
private String name;
private BigDecimal price;
private Category category;
//get/set
}
And builders
public class CustomerBuilder {
private Customer customer = new Customer();
public CustomerBuilder setName(String name){
customer.setName(name);
return this;
}
public CustomerBuilder setSurname(String surname){
customer.setSurname(surname);
return this;
}
public CustomerBuilder setAge(int age){
customer.setAge(age);
return this;
}
public CustomerBuilder setEmail(String email){
customer.setEmail(email);
return this;
}
public Customer build() {
return customer;
}
}
public class OrderBuilder {
private Order order = new Order();
public OrderBuilder setCustomer(Customer customer){
order.setCustomer(customer);
return this;
}
public OrderBuilder setProduct(Product product){
order.setProduct(product);
return this;
}
public OrderBuilder setQuantity(int quantity){
order.setQuantity(quantity);
return this;
}
public OrderBuilder setOrderDate(LocalDate orderDate){
order.setOrderDate(orderDate);
return this;
}
public Order build(){
return order;
}
}
public class ProductBuilder {
private Product product = new Product();
public ProductBuilder setCategory(Category category){
product.setCategory(category);
return this;
}
public ProductBuilder setName(String name){
product.setName(name);
return this;
}
public ProductBuilder setPrice(BigDecimal bigDecimal){
product.setPrice(bigDecimal);
return this;
}
public Product build() {
return product;
}
}
The following finds the top customer by first grouping by the customer
field (mapped to the total of corresponding purchase value [quantity * price
]).
The result of that aggregation is then traversed to find "max" by total purchase value.
Customer topCustomer = orders.stream()
.collect(Collectors.groupingBy(Order::getCustomer,
Collectors.mapping(
order -> order.getProduct()
.getPrice()
.multiply(new BigDecimal(order.getQuantity())),
Collectors.reducing(BigDecimal.ZERO, BigDecimal::add))))
.entrySet().stream()
.max(Comparator.comparing(Entry::getValue))
.map(Entry::getKey)
.orElse(null);
It's important to note that this assumes hashCode()
and equals()
are properly overridden in Customer
for the grouping to work correctly.
EDIT:
If the total amount of purchases is also required, you will need to get the full entry instead of mapping to just the key (below code is based on snippet above):
Optional<Entry<Customer, BigDecimal>> topCustomerEntry = orders.stream()
.collect(Collectors.groupingBy(Order::getCustomer,
Collectors.mapping(order ->
order.getProduct()
.getPrice()
.multiply(new BigDecimal(order.getQuantity())),
Collectors.reducing(BigDecimal.ZERO, BigDecimal::add))))
.entrySet().stream()
.max(Comparator.comparing(Entry::getValue));
BigDecimal topValue = null; //total value for top customer
Customer customer = null; //customer with most purchases
if(topCustomerEntry.isPresent()) {
topValue = topCustomerEntry.get().getValue();
customer = topCustomerEntry.get().getKey();
}
This will just print the values. But you can restructure the code to assign them to a variable.