Home > Software design >  Java Stream - Groupby based on child class and calculate sum from parent class
Java Stream - Groupby based on child class and calculate sum from parent class

Time:10-10

Below are my entities:

Product

@Entity
@Table(name = "Product")
public class Product extends ReusableFields
{

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    Long productId;

    @NonNull
    @Column(name = "product_name")
    String productName;
    String measurementUnit;
    //more fields and getters setters
}

Inward Outward List related to product:

@Entity
@Table(name = "inward_outward_entries")
public class InwardOutwardList extends ReusableFields
{
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    Long entryid;

    @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinColumn(name = "productId", nullable = false)
    @JsonIgnoreProperties(
    { "hibernateLazyInitializer", "handler" })
    Product product;
    
    @JsonSerialize(using = DoubleTwoDigitDecimalSerializer.class)
    Double quantity;
    //more fields
}

Inward inventory having set of inward outward list:

@Entity
@Table(name = "inward_inventory")
public class InwardInventory extends ReusableFields implements Cloneable
{

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "inwardid")
    Long inwardid;

    @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinTable(name = "inwardinventory_entry", joinColumns =
    { @JoinColumn(name = "inwardid", referencedColumnName = "inwardid") }, inverseJoinColumns =
    { @JoinColumn(name = "entryId", referencedColumnName = "entryId") })
    Set<InwardOutwardList> inwardOutwardList = new HashSet<>();

    //more fields

}

I have a list of inward inventories which I want to group by based on product. So, I want to do is

SUM(InwardInventory.InwardOutwardList.quantity) while grouping by based on InwardInventory.InwardOutwardList.Product.productName and InwardInventory.InwardOutwardList.Product.measurementUnit

I am new to streams, I know it can be done but not able to get exact solution. Can someone provide guidance or help.

CodePudding user response:

From what I understand, you want a Map where the key is some representantion of a unique productName and measurementUnit, and the value is a Double.

First, you need to define the class that you'll use as the map's keys. The requirement for it is to implement equals and hashCode ONLY based on productName and measurementUnit fields. Else, you won't be able to properly aggregate the quantities into the map. If you follow Vlad Mihalcea's advice then you can't use the Product class as keys because it implements equals and hashCode based only on the id field. In the example below Pair<L, R> has properly implemented equals and hashCode based on the L and R fields.

List<InwardInventory> inwardInventoryList = ...;

Map<Pair<String, String>, Double> map = inwardInventoryList.stream()
        .flatMap(i -> i.getInwardOutwardList().stream())
        .collect(Collectors.toMap(l -> Pair.of(l.getProduct().getProductName(), l.getProduct().getMeasurementUnit()),
                InwardOutwardList::getQuantity,
                Double::sum));

The first operation is a flatMap because every InwardInventory has a Set<InwardOutwardList>, but we want a single stream of InwardOutwardList objects.

The second operation is the Collectors.toMap defined here.

CodePudding user response:

I created simple classes from your code since the annotations are not required.

Class Product

public class Product {
    private Long  productId;
    private String  measurementUnit;
    private String  productName;

    public Product(Long productId, String measurementUnit, String productName) {
        this.productId = productId;
        this.measurementUnit = measurementUnit;
        this.productName = productName;
    }

    public Long getProductId() {
        return productId;
    }

    public String getMeasurementUnit() {
        return measurementUnit;
    }

    public String getProductName() {
        return productName;
    }
}

Class InwardOutwardList

public class InwardOutwardList {
    private Long  entryid;
    private Product  product;
    private Double quantity;

    public InwardOutwardList(Long entryid, Product product, Double quantity) {
        this.entryid = entryid;
        this.product = product;
        this.quantity = quantity;
    }

    public Long getEntryid() {
        return entryid;
    }

    public Product getProduct() {
        return product;
    }

    public Double getQuantity() {
        return quantity;
    }
}

Finally, class InwardInventory which contains method main that demonstrates how to use the stream API to achieve your desired result.
(Notes after the code.)

import java.util.DoubleSummaryStatistics;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public class InwardInventory {
    private Long  inwardid;
    private Set<InwardOutwardList>  inwardOutwardList = new HashSet<>();

    public InwardInventory(Long id) {
        inwardid = id;
    }

    public static void main(String[] args) {
        Product p1 = new Product(1L, "unit", "Product_1");
        Product p2 = new Product(1L, "unit", "Product_1");
        InwardOutwardList ioLst = new InwardOutwardList(1L, p1, 1D);
        InwardOutwardList ioLst2 = new InwardOutwardList(2L, p2, 2D);
        InwardInventory ii = new InwardInventory(1L);
        ii.inwardOutwardList.add(ioLst);
        ii.inwardOutwardList.add(ioLst2);
        Map<String, DoubleSummaryStatistics> map = ii.inwardOutwardList.stream()
                                                     .collect(Collectors.groupingBy(iol -> iol.getProduct().getProductName(),
                                                                                    Collectors.summarizingDouble(InwardOutwardList::getQuantity)));
        map.forEach((p, s) -> System.out.println(p   " = "   s.getSum()));
    }
}
  • ii.inwardOutwardList.stream() creates a Stream where every element in that stream is an instance of InwardOutwardList.
  • the collect method has a single argument whose type is Collector.
  • Class Collectors is a utility class that contains methods that return special kinds of collectors.
  • the collect method returns a Map where the map key is the product name – extracted from class Product and the map value is an instance of class DoubleSummaryStatistics.
  • method getSum, in class DoubleSummaryStatistics returns, in this case, the sum of the quantities.

Running above code produces the following result:

Product_1 = 3.0
  • Related