Search code examples
javacollectionsjava-8java-streamcollectors

Remove duplicate object in Java 8 and get the sum of String values in the object (first value need to convert in BigDecimal)


I have class DTO which have 3 fields.

And I have a list of DTO objects.

I want to do remove all the duplicate object based on ID field in the list and get the sum of the value which is string type. First values convert to Bigdecimal and then add(Sum) the duplicate object values.

Note:: Value is string format in the class DTO

@AllArgsConstructor
static class DTO {
    int id;
    int year;
    String value;
}

List<DTO> list = new ArrayList();
list.add(new DTO(1, 2020, "5.5"));
list.add(new DTO(1, 2020, "-8.0"));
list.add(new DTO(2, 2020, "1.5"));
list.add(new DTO(3, 2020, "4.5"));
list.add(new DTO(3, 2020, "1.5"));
list.add(new DTO(3, 2020, "-9.5"));
list.add(new DTO(4, 2020, "-3.5"));
list.add(new DTO(4, 2020, "7.5"));
list.add(new DTO(4, 2020, "5.5"));
list.add(new DTO(4, 2020, "-7.5"));

I've tried a variations of streams, collectors, groupby etc., and I'm lost.

This is what I have so far:

list.stream().collect(Collectors.groupingBy(e -> e.getId()), Collectors.reducing(BigDecimal.ZERO, DTO::getValue, BigDecimal.add()));

Expected

DTO(1, 2021, "-2.5"); (value Sum of group 1)
DTO(2, 2021, "-1.5"); (value Sum of group 2)
DTO(3, 2021, "-3.5"); (value Sum of group 3)
DTO(4, 2021, "2"); (value Sum of group 4)

Solution

  • You can create a method that adds two DTO:

    private static DTO add(DTO dto1, DTO dto2) {
        BigDecimal bd1 = new BigDecimal(dto1.getValue());
        BigDecimal bd2 = new BigDecimal(dto2.getValue());
        return new DTO(dto1.getId(), dto1.getYear(), bd1.add(bd2).toString());
    }
    

    Then you can stream grouping by id and reduce using the previous method:

    private static List<DTO> add(List<DTO> list) {
        Map<Integer, Optional<DTO>> map = list.stream()
            .collect(Collectors.groupingBy(DTO::getId, 
                    Collectors.reducing((d1, d2) -> add(d1, d2))));
    
        return map.values().stream()
                .filter(Optional::isPresent)
                .map(Optional::get).toList();
    }
    

    Test:

    List<DTO> list = List.of(
            new DTO(1, 2020, "5.5"),
            new DTO(1, 2020, "-8.0"),
            new DTO(2, 2020, "1.5"),
            new DTO(3, 2020, "4.5"),
            new DTO(3, 2020, "1.5"),
            new DTO(3, 2020, "-9.5"),
            new DTO(4, 2020, "-3.5"),
            new DTO(4, 2020, "7.5"),
            new DTO(4, 2020, "5.5"),
            new DTO(4, 2020, "-7.5"));
    
    List<DTO> listAdded = add(list);
        
    listAdded.forEach(System.out::println);
    

    Output:

    DTO[1, 2020, -2.5]
    DTO[2, 2020, 1.5]
    DTO[3, 2020, -3.5]
    DTO[4, 2020, 2.0]