Organize list of object in java

Viewed 142

I have a class

public class ProductStock {

   private Long id;
   private Integer quantity;

}

and a list like

List<ProductStock> productStocks = 
{
  ProductStock(1, 1),
  ProductStock(2, 1),
  ProductStock(3, 1),
  ProductStock(1, 1),
  ProductStock(4, 1),
  ProductStock(5, 1),
  ProductStock(2, 1)
}

I want to group productStocks by id. What is the best way to convert this list like bellow

productStocks = 
{
  ProductStock(1, 2),
  ProductStock(2, 2),
  ProductStock(3, 1),
  ProductStock(4, 1),
  ProductStock(5, 1)
}
4 Answers

With Java8 streams you could try it like this:

productStocks = new ArrayList<>( productStocks.stream().collect( 
       Collectors.toMap( p -> p.id, p -> p, (p,o) -> { p.quantity += o.quantity; return p;} ) )
                 .values() );

What this does:

  • productStocks.stream().collect( Collectors.toMap(...) ) creates a stream for the list and collects the values into a map.
  • p -> p.id uses the product's id as the map key
  • p -> p uses the product as the map value, if it doesn't exist already
  • (p,o) -> { p.quantity += o.quantity; return p;} merges the existing product p and the "new" product value o by adding the quantity into p and returning that. Alternatively you could create a new Product instance: (p,o) -> new Product(p.id, p.quantity + o.quantity)
  • finally we build a new list out of the map's values

Note that collecting the elements into a map like like might not preserve the order of the elements. If you want to keep the order as defined in the source list, you could add a 4th parameter to collect(...): LinkedHashMap::new.

You can group the products by iterating the List and add products to LinkedHashMap to preserve order:

void mergeProducts() {
    Map<Long, Integer> pMap = new HashMap<Long, Integer>();
    for (ProductStock ps :  productStocks) {
        if (pMap.containsKey(ps.getId())) {
            int qty = pMap.get(ps.getId());
            qty += ps.getQuantity();
            pMap.put(ps.getId(), qty);
        } else {
            pMap.put(ps.getId(), ps.getQuantity());
        }
    }

    System.out.println("Product grouping: ");
    for (Map.Entry<Long, Integer> entry : pMap.entrySet()) {
        System.out.println(entry.getKey() + " " + entry.getValue());
    }
}

I'd do it that way:

Map<Long, Integer> counting = productStocks.stream().collect(
                Collectors.groupingBy(ProductStock::getId, Collectors.counting()));

Create an new Map with the id as key and the Count as value.

You can use a Map for aggregating all the ProductStock in one place. Then you can use the contents of this Map to create the new List of ProductStock that will have the aggregated data.

Map<Long, Long> productStockMap = productStocks.stream().collect(Collectors.groupingBy(ProductStock::getId, Collectors.counting()));
productStocks = new ArrayList<>();
for(Map.Entry<Long, Long> entry: productStockMap.entrySet()) {
    productStocks.add(new ProductStock(entry.getKey(), entry.getValue().intValue()));
}

Result:

============Before============

ProductStock{id=1, quantity=1}
ProductStock{id=2, quantity=1}
ProductStock{id=3, quantity=1}
ProductStock{id=1, quantity=1}
ProductStock{id=4, quantity=1}
ProductStock{id=5, quantity=1}
ProductStock{id=2, quantity=1}

=============After============

ProductStock{id=1, quantity=2}
ProductStock{id=2, quantity=2}
ProductStock{id=3, quantity=1}
ProductStock{id=4, quantity=1}
ProductStock{id=5, quantity=1}
Related