Skip to Content
Course content

78: Practice Exercise: Processing a Dataset with Streams and Collectors

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

When I first started using the Stream API, I fell into a trap that I see a lot of developers stumble into today. I thought that calling a method like .filter() or .map() on a stream was actually modifying the collection I started with. It felt intuitive—if I tell Java to "filter" my list of users to find only the active ones, I expected the original list to be pruned down to those active users.

The Myth: Streams Modify the Source Collection

Let's look at why that thinking is dangerous. If you believe streams are destructive, you'll write code that is logically broken. Consider this snippet where we're trying to isolate "High Value" orders from a shopping cart:

List<Order> orders = getOrderHistory(); 
// I think this removes all cheap orders from the 'orders' list
orders.stream()
      .filter(o -> o.getTotal() > 100.00)
      .forEach(System.out::println);

System.out.println("Remaining orders: " + orders.size()); 
// Surprise: the size is exactly the same as it was before!

The orders list didn't change. Not one item was removed. The filter operation didn't "delete" anything; it simply created a new, temporary "view" of the data that only allowed certain elements to pass through to the next stage of the pipeline. If you don't capture the result of that pipeline into a new collection, the work the stream did effectively vanishes into the ether once the terminal operation finishes.

The Reality: Pipelines are Non-Destructive Transformers

Think of a Stream not as a list, but as a conveyor belt. The original collection is the warehouse where the items live. When you call .stream(), you're just copying items from the warehouse onto the belt. You can paint them (.map()), throw some off the belt (.filter()), or sort them, but the items still sitting back in the warehouse remain completely untouched.

To actually "keep" the results of your processing, you need a Collector. The collector is the box at the end of the conveyor belt that catches the items and puts them into a new structure. This is why .collect(Collectors.toList()) or .collect(Collectors.groupingBy(...)) are so critical—they are the only way to materialize the results of your stream back into a usable Java object.

Organizing Chaos with groupingBy

Once you realize that streams are about transformation rather than modification, the real power comes from complex collectors. I personally find Collectors.groupingBy to be the "Swiss Army Knife" of data processing. It allows you to turn a flat list into a Map, effectively categorizing your data on the fly.

Suppose you have a list of Product objects and you want to group them by category. Instead of writing a nested for-each loop and manually initializing lists inside a map, you do this:

Map<Category, List<Product>> productsByCategory = products.stream()
    .collect(Collectors.groupingBy(Product::getCategory));

I've seen this replace 20 lines of boilerplate code with a single, readable statement. You're telling Java: "Take this stream, look at the category of each item, and organize them into a map where the keys are categories and the values are lists of products belonging to those categories." It's clean, it's functional, and most importantly, it leaves your original products list exactly as you found it.




📋 Practical Task

Exercise: Analyze Employee Departmental Spending

You are tasked with building a small reporting tool for an HR department. You have a list of Employee objects, and you need to calculate the total salary expenditure for each department.

Requirements:

  • Create a class Employee with fields: String name, String department, and double salary.
  • Create a List<Employee> containing at least five employees across at least three different departments (e.g., "Engineering", "Sales", "HR").
  • Use a Java Stream to process this list.
  • Use Collectors.groupingBy in combination with Collectors.summingDouble to create a Map<String, Double> where the key is the department name and the value is the total sum of salaries for that department.
  • Print the resulting map to the console to verify the totals.

Constraints: Your solution must use a single stream pipeline to arrive at the final Map; do not use manual loops to sum the salaries.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.