Explain Codes LogoExplain Codes Logo

How can I count occurrences with groupBy?

java
collectors
groupby
stream-api
Nikita BarsukovbyNikita Barsukov·Dec 1, 2024
TLDR

Here's a quick way to count occurrences in a list using Java Streams and groupingBy:

Map<Key, Long> frequencyMap = items.stream() .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));

Don't forget to replace Key with your data type and items with your collection. The Function.identity() defines the criteria we are grouping by—in this case, the item itself.

GroupBy in Detail

Ugh, let's unwrap this 'magic'! The Collectors.groupingBy performs SQL-like operations on the data. It takes two arguments:

  • The classifier function, used to determine which 'group' each item belongs to.
  • A downstream collector, such as Collectors.counting() for count operations.

Counting custom object

What if we had a party of custom objects and wanted to count them based on certain attributes? You could call in Collectors.groupingBy to sort them out!

Map<CustomType, Long> counts = customList.stream() .collect(Collectors.groupingBy(CustomType::getAttribute, Collectors.counting()));

Concurrent Counting

Does your code run in a race? In a multi-threaded environment, consider groupingByConcurrent for thread-safe operations:

ConcurrentMap<Key, Long> concurrentFrequencyMap = items.parallelStream() .collect(Collectors.groupingByConcurrent(Function.identity(), Collectors.counting()));

That's right. No more crashes during the race!

Performance and Alternatives

Collectors.groupingBy is convenient, but there might be scenarios where it's not the fastest kid on the block.

ToMap to the Rescue

If groupingBy is the tortoise, Collectors.toMap with Map::merge is the hare:

Map<Key, Long> frequencyMapAlternative = items.stream() .collect(Collectors.toMap(key -> key, val -> 1L, Math::addExact));

But remember, it's not a race, right?

StreamEx for primitive types

If high-performance integer-based counting is your thing, say hello to the StreamEx library that offers MoreCollectors.countingInt():

Map<Key, Integer> frequencyMapStreamEx = StreamEx.of(items) .groupingBy(Function.identity(), MoreCollectors.countingInt());

Friendly note: you gotta have the StreamEx library installed for this.

Good ol' printing

Want to display these counts? Sure, break out the entrySet:

frequencyMap.entrySet().forEach(entry -> System.out.println(entry.getKey() + ": " + entry.getValue()));

Who knew your console could double as a gossip magazine for your data?

Advanced Cases

Reality can be messy. Nulls, complex types, and further processing... we've got you covered.

Null handling

Nulls. Those little cretins can mess up your streamline operations:

Map<Object, Long> frequencyMapWithNulls = items.stream() .filter(Objects::nonNull) // Nullbsutbuster in action .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));

That's right, Ghostbusted!

Complex types

For complex objects, create a compound key or a Pair:

Map<Pair<AttributeType1, AttributeType2>, Long> complexMap = complexList.stream() .collect(Collectors.groupingBy( item -> new AbstractMap.SimpleEntry<>(item.getAttribute1(), item.getAttribute2()), Collectors.counting() ));

It's like organizing chaos, only easier!

Post-processing

Want to play scientist with your frequency map? You can sort, filter, or transform the results for in-depth analytics.