-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
78: Practice Exercise: Processing a Dataset with Streams and Collectors
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
Employeewith fields:String name,String department, anddouble 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.groupingByin combination withCollectors.summingDoubleto create aMap<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.
There are no comments for now.