Java stream collect counting to field

You could collect directly to NameGroup.count, but it would be less efficient than what you have, not more.

Internally, the map is being used to maintain a data structure that can efficiently track the name combinations and map them to counts which are updated as more matches are found. Reinventing that data structure is painful and unlikely to result in meaningful improvements.

You could try to collect NameGroups directly in the map instead of going via a count, but most approaches for that would, again, be more expensive than what you have now, and certainly much more awkward.

Honestly: what you have now is perfectly good, and not inefficient in any ways that are important. You should almost certainly stick to what you have.


Not very clean but you can possibly do it as :

List<NameGroup> convertUsersToNameGroups(List<User> users) {
    return new ArrayList<>(users.stream()
            .collect(Collectors.toMap(p -> Arrays.asList(p.getFirstName(), p.getLastName()),
                    p -> new NameGroup(p.getFirstName(), p.getLastName(), 1L),
                    (nameGroup1, nameGroup2) -> new NameGroup(nameGroup1.getFirstName(), nameGroup1.getLastName(),
                            nameGroup1.getCount() + nameGroup2.getCount()))).values());
}

You can minimize allocations of intermediate objects, e.g. all the Arrays.asList(...) objects, by build a map yourself, instead of using streaming.

This relies on the fact that your NameGroup is mutable.

To even make the code simpler, lets add two helpers to NameGroup:

public static class NameGroup {
    // fields here

    public NameGroup(User user) {
        this.firstName = user.getFirstName();
        this.lastName = user.getLastName();
    }

    public void incrementCount() {
        this.count++;
    }

    // other constructors, getters and setters here
}

With that in place, you can implement the logic like this:

Map<User, NameGroup> map = new TreeMap<>(Comparator.comparing(User::getFirstName)
                                                   .thenComparing(User::getLastName));
users.stream().forEach(user -> map.computeIfAbsent(user, NameGroup::new).incrementCount());
List<NameGroup> names = new ArrayList<>(map.values());

Or if you don't actually need a list, the last line can be simplified to:

Collection<NameGroup> names = map.values();