Skip to content
Advertisement

Java 8 Streams Map Reduced Value after Group By

Using Java streams, I would like to calculate the cost given a list of items, grouped by type. After getting the cost, I would like to map the BigDecimal to a formatted currency String. Is it possible to map reduced and grouped values?

import java.math.BigDecimal;
import java.util.List;
import java.util.ArrayList;
import java.util.stream.Collectors;
import java.util.Map;

public class MyClass {
    public static class Item {
        private String name;
        private String type;
        private BigDecimal cost;
        
        public Item(String name, String type, BigDecimal cost) {
            this.name = name;
            this.type = type;
            this.cost = cost;
        }
        
        public String getName() {
            return name;
        }
        
        public String getType() {
            return type;
        }
        
        public BigDecimal getCost() {
            return cost;
        }
        
    }

    public static void main(String args[]) {
      List<Item> items = new ArrayList<>();
      items.add(new Item("Doll", "Toy", new BigDecimal("2.00")));
      items.add(new Item("Hamburger", "Food", new BigDecimal("8.00")));
      items.add(new Item("Car", "Toy", new BigDecimal("4.00")));
      items.add(new Item("Salad", "Food", new BigDecimal("1.00")));
      
      Map<String, BigDecimal> itemTypesAndCosts = items.stream().collect(Collectors.groupingBy(Item::getType, Collectors.reducing(BigDecimal.ZERO, Item::getCost, BigDecimal::add)));
      
      // prints {Food=9.00, Toy=6.00}
      // I would like it to map big decimal to formatted string to print {Food="$9.00", Toy="$6.00"}
      
      System.out.println(itemTypesAndCosts);
    }
}

Advertisement

Answer

You can wrap your reducing collector in CollectingAndThen collector which takes a downstream collector and a finisher function. CollectingAndThen is a special collector that allows us to perform another action on a result straight after collecting ends. Change your map type to Map<String, String> and do :

Map<String, String> itemTypesAndCosts = items.stream().collect(Collectors.groupingBy(Item::getType,
        Collectors.collectingAndThen(
                Collectors.reducing(BigDecimal.ZERO, Item::getCost, BigDecimal::add),
                total -> new DecimalFormat("'$'0.00").format(total))
));

//output: {Food=$9.00, Toy=$6.00}
9 People found this is helpful
Advertisement