Java
Completed
-
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
71: Intermediate Stream Operations
By now, you're comfortable with filter and map. They're the bread and butter of Streams. But real-world data is rarely that clean. Usually, you're dealing with nested collections—lists inside lists—or you need to prune and organize your data before it hits the terminal operation. That's where intermediate operations like flatMap, distinct, and sorted come into play.
Let's build something practical. Imagine we're building a reporting tool for an e-commerce store. We have a list of Order objects, and each order contains a list of LineItem objects. Our goal is to generate a unique, alphabetized list of every product category that was sold today. This sounds simple, but the nesting makes it a bit tricky.
Defining our Domain
I'll keep the POJOs slim so we can focus on the Stream logic. We have an Order that holds a List<LineItem>, and a LineItem that has a category string.
record LineItem(String product, String category, double price) {}
record Order(int orderId, List<LineItem> items) {}
// Our sample data
List<Order> orders = List.of(
new Order(1, List.of(new LineItem("Laptop", "Electronics", 1200), new LineItem("Mouse", "Electronics", 25))),
new Order(2, List.of(new LineItem("Desk Chair", "Furniture", 150), new LineItem("Lamp", "Furniture", 45))),
new Order(3, List.of(new LineItem("Keyboard", "Electronics", 80), new LineItem("Notebook", "Stationery", 5)))
);
The "Nested Stream" Trap
My first instinct—and this is a mistake I still make when I'm rushing—is to use a standard map to get the items. Let's see what happens when I try to extract the categories using map.
var categories = orders.stream()
.map(order -> order.items().stream()
.map(LineItem::category))
.toList();
If you try to compile this, you'll notice something annoying. I didn't get a List<String>. I got a List<Stream<String>>. Because order.items().stream() returns a stream, map just wraps that stream inside another stream. It's a "stream of streams," which is almost never what you actually want. It's an architectural mess to work with.
Flattening the Hierarchy with flatMap
This is exactly why flatMap exists. While map transforms one element into another single element, flatMap transforms one element into a stream of elements, and then "flattens" all those resulting streams into one single, continuous stream.
I'll swap out that map for flatMap. Now, instead of a list of streams, we get one long stream of every LineItem across every single order.
var categories = orders.stream()
.flatMap(order -> order.items().stream()) // Now we have a Stream<LineItem>
.map(LineItem::category) // Transform LineItem to String (category)
.toList();
We're getting closer, but if you run this, you'll see "Electronics" appearing multiple times. In a report, that's noise. We only care about the unique categories.
Pruning and Organizing the Results
To clean this up, we use distinct(). It uses the equals() method of the objects in the stream to strip out duplicates. But since we want a professional-looking report, we shouldn't just leave the list in whatever random order the data arrived. We'll chain sorted() at the end.
Here is the final, polished pipeline:
List<String> uniqueCategories = orders.stream()
.flatMap(order -> order.items().stream())
.map(LineItem::category)
.distinct()
.sorted()
.toList();
System.out.println(uniqueCategories);
// Output: [Electronics, Furniture, Stationery]
One quick tip: notice that I put distinct() before sorted(). Always try to reduce the size of your stream as early as possible. Sorting a list of 10 unique categories is much faster than sorting a list of 1,000 redundant ones. It's a small optimization, but it's a habit that will save you when you're dealing with massive datasets.
📋 Practical Task
Exercise: Processing a Student Course Registry
You are building a system for a university registrar. You have a list of Student objects, and each student has a Set<String> of courses they are currently enrolled in. Some students might be taking the same courses, and the course names are currently inconsistently cased (e.g., "java 101" and "JAVA 101").
Your Goal: Write a Stream pipeline that produces a sorted list of all unique courses offered across the entire student body, ensuring all course names are converted to uppercase to avoid duplicates caused by casing.
Starting Data:
record Student(String name, Set<String> courses) {}
List<Student> students = List.of(
new Student("Alice", Set.of("Java 101", "Algorithms")),
new Student("Bob", Set.of("java 101", "Databases")),
new Student("Charlie", Set.of("Algorithms", "Calculus")),
new Student("Dave", Set.of("DATABASES", "Java 101"))
);
Requirements:
- Use
flatMapto access the courses. - Ensure the final list contains no duplicates regardless of case.
- The final list must be sorted alphabetically.
- The final result should be a
List<String>.
There are no comments for now.