Refactoring a Loop into a Pipeline

Streams · lesson 38 of 42 · 5 min read

A mechanical recipe for turning a nested report loop into one readable pipeline.

Open this lesson in the learning hub

Key points

  • Step 1: name the source. Whatever the outer for walks becomes the .stream().
  • Step 2: every guard, every if (...) continue;, becomes a filter.
  • Step 3: every "build a smaller thing from this element" line becomes a map.
  • Step 4: the accumulator declared above the loop names the collector: List, Map, sum or join.
  • Step 5: a nested inner loop becomes flatMap, and a keyed accumulator becomes groupingBy.
  • Stop if the result reads worse. The recipe is a tool, not an obligation.

Example

import java.util.*;
import java.util.stream.*;

public class Main {
    record Order(String region, List<Integer> lines) {}

    public static void main(String[] args) {
        List<Order> orders = List.of(
                new Order("eu", List.of(20, 30)),
                new Order("us", List.of(5)),
                new Order("eu", List.of(50)),
                new Order("apac", List.of()));

        // BEFORE: an accumulator, a guard, a nested loop and a running total.
        Map<String, Integer> before = new TreeMap<>();
        for (Order o : orders) {
            if (o.lines().isEmpty()) {
                continue;
            }
            int total = 0;
            for (int line : o.lines()) {
                total += line;
            }
            before.merge(o.region(), total, Integer::sum);
        }

        // AFTER: guard -> filter, inner loop -> a nested sum, accumulator -> groupingBy.
        Map<String, Integer> after = orders.stream()
                .filter(o -> !o.lines().isEmpty())
                .collect(Collectors.groupingBy(Order::region, TreeMap::new,
                        Collectors.summingInt(o -> o.lines().stream().mapToInt(Integer::intValue).sum())));

        System.out.println("before : " + before);
        System.out.println("after  : " + after);
        System.out.println("same   : " + before.equals(after));

        // The nested loop on its own is nothing but a flatMap.
        System.out.println("lines  : " + orders.stream().flatMap(o -> o.lines().stream()).toList());
    }
}

Guard becomes filter, inner loop becomes flatMap, accumulator becomes the collector.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Streams course, and every lesson in it is listed on the Streams contents page.