I have a list of products with amount one of the attributes. And the list can contain common product name with other attributes being different. So i want to group the list by product and the sum of the amount of the products which share common name in java 8 using grouping and Summing.
Example:
[
{
"name":"Product A",
"amount":"40.00",
"description":"Info1",
"number":"65"
},
{
"name":"Product A",
"amount":"50.00",
"description":"Info2",
"number":"67"
},
{
"name":"Product A",
"amount":"100.00",
"description":"Info3",
"number":"87"
},
{
"name":"Product B",
"amount":"45.00",
"description":"Info4",
"number":"86"
},
{
"name":"Product D",
"amount":"459.00",
"description":"Info5",
"number":"7"
},
{
"name":"Product B",
"amount":"50.00",
"description":"Info6",
"number":"8"
}
]
The output should be similar to this:
{
"Product A = 190.00":[
{
"name":"Product A",
"amount":"40.00",
"description":"Info1",
"number":"65"
},
{
"name":"Product A",
"amount":"50.00",
"description":"Info2",
"number":"67"
},
{
"name":"Product A",
"amount":"100.00",
"description":"Info3",
"number":"87"
}
],
"Product B=95.00":[
{
"name":"Product B",
"amount":"45.00",
"description":"Info4",
"number":"86"
},
{
"name":"Product B",
"amount":"50.00",
"description":"Info6",
"number":"8"
}
],
"Product D = 459.00":[
{
"name":"Product D",
"amount":"459.00",
"description":"Info5",
"number":"7"
}
]
I have created a bean class ProductBean
which have all the fields (name, amount, description and number) and getters and setters for the same.
And productBeans has list of all the products.
Map<String, List<ProductBean>> groupByProduct =
productBeans.stream()
.collect(Collectors.groupingBy(item -> item.getName()))
Map<String, BigDecimal> result =
productBeans.stream()
.collect(Collectors.groupingBy(ProductBean::getName,
Collectors.mapping(ProductBean::getAmount,
Collectors.reducing(BigDecimal.ZERO, BigDecimal::add))));
groupByProduct has list of products grouped with name. result gives the map of product as the key and total amount of that product as value.
But here i'm trying to map the product and total amount to the list of products. I tried to combine the above code to get the expected output but not able to achieve. And same has been achieved by iterating through the map.
But any help in merging the above code to get the map in which product name and amount as key and list as value would be helpful.
It seems that you want something as such of Map<String, BigDecimal, List<ProductBean>>
where the String represents the name
, the BigDecimal
being the summation of all the Product's amount and List<ProductBean>
being the list of products in that specific group.
such a map is not possible, so instead, you've managed to do it in two steps which is not very efficient and it doesn't keep related data together rather it separates it into two maps.
My suggestion is to create a wrapper class as such:
class ResultSet {
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BigDecimal getAmount() {
return amount;
}
public void setAmount(BigDecimal amount) {
this.amount = amount;
}
public List<ProductBean> getProductBeans() {
return productBeans;
}
public void setProductBeans(List<ProductBean> productBeans) {
this.productBeans = productBeans;
}
private String name;
private BigDecimal amount;
private List<ProductBean> productBeans;
@Override
public String toString() {
return "ResultSet{" +
"name='" + name + '\'' +
", amount=" + amount +
", productBeans=" + productBeans +
'}';
}
}
Which will wrap a given group of ProductBean's
maintaining related data both for better maintainability and arguably readability.
Now, you can accomplish the task at hand with:
List<ResultSet> result = productList.stream()
.collect(Collectors.groupingBy(ProductBean::getName))
.entrySet()
.stream()
.map(e -> {
ResultSet resultSet = new ResultSet();
BigDecimal sum = e.getValue().stream()
.map(ProductBean::getAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
resultSet.setName(e.getKey());
resultSet.setAmount(sum);
resultSet.setProductBeans(e.getValue());
return resultSet;
}).collect(Collectors.toList());
This groups by the ProductBean
name, and then maps each group, to a ResultSet
instance which will encapsulate the name, summation of the amount in that specific group and the entire ProductBean's
in the group.
You can further refactor the above stream query by creating a method for the mapping:
private static ResultSet mapToResultSet(Map.Entry<String, List<ProductBean>> e) {
ResultSet resultSet = new ResultSet();
BigDecimal sum = e.getValue().stream()
.map(ProductBean::getAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
resultSet.setName(e.getKey());
resultSet.setAmount(sum);
resultSet.setProductBeans(e.getValue());
return resultSet;
}
Thus, rendering the stream query to:
List<ResultSet> result = productList.stream()
.collect(Collectors.groupingBy(ProductBean::getName))
.entrySet()
.stream()
.map(Main::mapToResultSet) // Main representing the class containing the mapToResultSet method
.collect(Collectors.toList());