Java - Merge objects of list given a condition

Guillermo Nahuel Varelli :

I uttlerly convinced that my question its quite simple but im unable to do it with streams (if there is a way to do it without stream will be helpfull too) Suppose that we have this list of users

public class Users {
   String firstName;
   String lastName;
   double accountBalance;
   String type;
   String extraField;
}

and suppose that we have the following data in my List < Users >

"Users": [{
            "firstName": "Scott",
            "lastName": "Salisbury",
            "accountBalance": "100",
            "type" : "A"
        }, {
            "firstName": "John",
            "lastName": "Richards",
            "accountBalance": "200",
            "type" :"C"

        }, {
            "firstName": "John",
            "lastName": "Richards",
            "accountBalance": "200",
            "type " : "C",
            "ExtraField": "Apply"
        }]

the expected result here its given that firstName, lastName and type appears twice on the list just merge the results that are common without missing any field
Expected output

"Users": [{
            "firstName": "Scott",
            "lastName": "Salisbury",
            "accountBalance": "100",
            "type" : "A"
        }, {
            "firstName": "John",
            "lastName": "Richards",
            "accountBalance": "400",//merged values
            "type " : "C",
            "ExtraField": "Apply" //value that remains in one object of the list
        }]  
daniu :

You can create a key class containing the three fields, like

@Data
class UserKey {
    String firstName;
    String lastName;
    String type;

    static UserKey from(User user) { /* TODO (trivial) */ }
}

groupingBy

Those can be used to group your users

Map<UserKey,List<User>> grouped = 
    users.stream().collect(Collectors.groupingBy(UserKey::from));

Each of these lists can then be merged by

Optional<User> summed = userList.stream()
    .collect(Collectors.reducing((u1, u2) -> {
        u1.setAccountBalance(u1.accountBalance() + u2.accountBalance());
    });

This can also be given directly as a downstream collector to the groupingBy:

Map<UserKey,Optional<User>> mergedMap = 
    users.stream().collect(Collectors.groupingBy(UserKey::from,
        Collectors.reducing((u1, u2) -> {
            u1.setAccountBalance(u1.accountBalance() + u2.accountBalance());
            return u1;
        }));

Since those Optionals are guaranteed to be filled, you can just call get() on them; also, you don't need the keys anymore, so

List<User> result = mergedMap.values().stream()
                 .map(Optional::get)
                 .collect(toList());

toMap

As Naman suggested in the comments, you can also shortcut this by toMap.

Map<UserKey,User> mergedMap = users.stream()
    .collect(toMap(UserKey::from, Function.identity(), 
        (u1, u2) -> {
            u1.setAccountBalance(u1.accountBalance() + u2.accountBalance());
            return u1;
        }));
List<User> result = new ArrayList<>(mergedMap.values());

Note that the reducing function has the side effect of manipulating one of the original user objects in the list, so make sure you don't need them again.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=21819&siteId=1