Is it possible use Collectors.groupingBy()
with Collectors.counting()
to count to the field of a custom object instead of creating a map and transforming it afterwards.
I have a list of users, like this:
public class User { private String firstName; private String lastName; // some more attributes // getters and setters }
I want to count all users with the same first and last name. Therefore I have custom object looking like this:
public static class NameGroup { private String firstName; private String lastName; private long count; // getters and setters }
I can collect the name groups using this:
List<NameGroup> names = users.stream() .collect(Collectors.groupingBy(p -> Arrays.asList(p.getFirstName(), p.getLastName()), Collectors.counting())) .entrySet().stream() .map(e -> new NameGroup(e.getKey().get(0), e.getKey().get(1), e.getValue())) .collect(Collectors.toList());
With this solution I have to group the users first to a map and transform it afterwards to my custom object. Is it possible to count all names directly to nameGroup.count
to avoid iterating twice over the list (or map) and improve the performance?
Answer
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();