Skip to Content
Course content

71: Intermediate Stream Operations

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

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 flatMap to 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>.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.