Skip to Content
Course content

74: Grouping and Partitioning with Streams

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I once worked with a developer who spent an entire afternoon writing a complex set of nested for loops and if statements just to categorize a list of customer orders by their shipping region. He was manually initializing HashMaps, checking if keys existed, creating new ArrayLists on the fly, and adding items one by one. It was about forty lines of boilerplate that was incredibly fragile. When I showed him how to do the exact same thing in a single line using Collectors.groupingBy, he actually stared at the screen in silence for a few seconds. It's one of those "lightbulb" moments in Java where the language stops feeling like a chore and starts feeling like a tool.

Organizing Data into Buckets with groupingBy

At its core, groupingBy is used when you want to take a flat stream of objects and turn it into a Map where the keys are some property of the object, and the values are lists of the objects that share that property. Think of it as sorting a deck of cards into piles by suit.

Let's say we're dealing with a Transaction class that has a currency field. If you have a list of a thousand transactions and you want to see all transactions grouped by their currency, you don't need to manage the map yourself. You just tell the stream how to derive the key.

Map<String, List<Transaction>> transactionsByCurrency = transactions.stream()
    .collect(Collectors.groupingBy(Transaction::getCurrency));

That's it. Java handles the map creation and the list appending behind the scenes. I personally find this much cleaner because it describes what you want to happen rather than how to manipulate the map pointers.

The Boolean Split with partitioningBy

Now, you might be tempted to use groupingBy for everything, but there's a specialized version called partitioningBy. This is specifically for when your "grouping" is actually a binary choice—a true or false. While groupingBy returns a Map<K, List<T>>, partitioningBy always returns a Map<Boolean, List<T>>.

Imagine you need to separate your transactions into two piles: those that are flagged for fraud review and those that aren't. Using a predicate makes this incredibly readable:

Map<Boolean, List<Transaction>> fraudCheck = transactions.stream()
    .collect(Collectors.partitioningBy(t -> t.getAmount() > 10000));

In this map, the true key will hold all high-value transactions, and the false key will hold everything else. It's more efficient than groupingBy because the JVM knows there are only ever two possible keys.

Refining Results with Downstream Collectors

The real power comes when you realize that groupingBy can take a second argument: a "downstream" collector. By default, it just collects items into a List, but what if you don't actually need the list? What if you just want to know how many transactions occurred in each currency?

Instead of grouping them into a list and then calling .size() on every entry in the map, you can pass Collectors.counting() as the second argument. This transforms the result from a Map<String, List<Transaction>> to a Map<String, Long>.

Map<String, Long> countsByCurrency = transactions.stream()
    .collect(Collectors.groupingBy(
        Transaction::getCurrency, 
        Collectors.counting()
    ));

You can do this with almost any collector. Want the most expensive transaction per currency? Use Collectors.maxBy. Want the average transaction amount per currency? Use Collectors.averagingDouble. It allows you to perform complex data analysis in a functional style without ever writing a manual loop.




📋 Practical Task

Analyzing E-Commerce Order Distributions

You are tasked with creating a report for a logistics manager. You have a list of Order objects, where each Order has a String city, a double totalValue, and a boolean isExpressShipping.

Write a method that processes a List<Order> and returns the following two pieces of data:

  • A Map<String, Long> containing the total number of orders placed in each city.
  • A Map<Boolean, List<Order>> partitioning the orders into those that used express shipping and those that did not.

Constraints: You must use Java Streams and the Collectors utility class. Avoid using any manual for or while loops to group the data.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.