'Java Stream API - Merge objects in collection if two fields are the same [closed]

I have a class Member:

public class Member {
 private Integer id; 
 private Integer age; 
 private Double amount; 
}

And what I want to do is transform the following input into output:

Input : 
[id =10, age =20, amount= 100.00]
[id =10, age =20, amount= 200.00]
[id =11, age =22, amount= 100.00]
[id =11, age =22, amount= 100.00]
[id =12, age =20, amount= 100.00]
Output :
[id =10, age =20, amount= 300.00]
[id =11, age =22, amount= 200.00]
[id =12, age =20, amount= 100.00]


Solution 1:[1]

Firstly, group List<Member> by id and age then sum Member which is in the same group.

Member member = new Member(10, 20, 100.00);
Member member1 = new Member(10, 20, 200.00);
Member member2 = new Member(11, 22, 100.00);
Member member3 = new Member(11, 22, 100.00);
Member member4 = new Member(12, 20, 100.00);
        
List<Member> members = Stream.of(member, member1, member2, member3, member4).collect(Collectors.toList());
Map<AbstractMap.SimpleEntry<Integer, Integer>, List<Member>> mapMembers = members.stream()
                .collect(Collectors.groupingBy(i -> new AbstractMap.SimpleEntry<>(i.getId(), i.getAge())));
        
Map<AbstractMap.SimpleEntry<Integer, Integer>, Member> map = mapMembers.entrySet()
        .stream()
        .collect(Collectors.toMap(Map.Entry::getKey,
                        u -> new Member(
                                u.getKey().getKey(),
                                u.getKey().getValue(),
                                u.getValue()
                                        .stream()
                                        .map(Member::getAmount)
                                        .mapToDouble(Double::doubleValue)
                                        .sum()
                        )
                ));
        
System.out.println(map.values());

Solution 2:[2]

Notes after the code.

import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Member {
    private int id; 
    private int age; 
    private double amount;

    public Member(int id, int age, double amount) {
        this.id = id;
        this.age = age;
        this.amount = amount;
    }

    public int getId() {
        return id;
    }

    public double getAmount() {
        return amount;
    }

    public String display() {
        return String.format("%d %d", id, age);
    }

    public boolean equals(Object obj) {
        boolean equal = this == obj;
        if (!equal) {
            if (obj != null) {
                if (getClass().equals(obj.getClass())) {
                    Member other = (Member) obj;
                    equal = id == other.id  &&  age == other.age;
                }
            }
        }
        return equal;
    }

    public int hashCode() {
        return display().hashCode();
    }

    public String toString() {
        return String.format("%d %d %.2f", id, age, amount);
    }

    public static void main(String[] args) {
        Stream.of(new Member(10, 20, 100.00),
                  new Member(10, 20, 200.00),
                  new Member(11, 22, 100.00),
                  new Member(11, 22, 100.00),
                  new Member(12, 20, 100.00))
              .collect(Collectors.groupingBy(Function.identity(),
                                             Collectors.reducing(0.0d,
                                                                 m -> m.getAmount(),
                                                                 (sum, amt) -> sum += amt)))
              .forEach((key, val) -> System.out.printf("%s %s%n", key.display(), val));
    }
}

I completed class Member by adding a constructor and other methods that are required in order to achieve your desired result.

In method main, the groupingBy is used to generate a Map where the [map] key is an instance of class Member. It calls [overridden] method hashCode, in class Member, in order to check for equal keys.

It is recommended to also override method equals in a class that overrides method hashCode.

Without the reducing method, the Map value would be a list of all Member instances with the same id and age. The reducing method sums the amount from each Member in a single list.

Running the above code produces the following output:

12 20 100
10 20 300
11 22 200

Solution 3:[3]

To group by your members by id and age and get the total grouped-by amount, I would:

  • Collect the results in a Map, where each entry's key is the Member object while its value the corresponding amount.

  • In case of colliding keys I would just account the amounts.

  • Finally, I would stream the Map's entrySet and map each entry to a new Member instantiated with the key's id and age and the total amount from the entry's value.

Moreover, in order to use a Member as a Map's key, it is required that the Member class overrides the equals() and hashCode() methods, so that two members are considered equal only by their id and age (the grouping by values).

Main

public class Main {
    public static void main(String[] args) {
        List<Member> list = Stream.of(new Member(10, 20, 100.00),
                        new Member(10, 20, 200.00),
                        new Member(11, 22, 100.00),
                        new Member(11, 22, 100.00),
                        new Member(12, 20, 100.00))
                .collect(Collectors.toMap(Function.identity(), m -> m.getAmount(), (leftValue, rightValue) -> leftValue + rightValue))
                .entrySet().stream()
                .map(e -> new Member(e.getKey().getId(), e.getKey().getAge(), e.getValue()))
                .collect(Collectors.toList());

        System.out.println(list);
    }
}

Member

class Member {
    private int id;
    private int age;
    private double amount;

    public Member(int id, int age, double amount) {
        this.id = id;
        this.age = age;
        this.amount = amount;
    }

    //... getters and setters

    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null) return false;
        if (obj.getClass() != getClass()) return false;
        Member m = (Member) obj;
        return id == m.id && age == m.age;
    }

    public int hashCode() {
        return Objects.hash(id, age);
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 logbasex
Solution 2
Solution 3