Skip to content

Intermediate · Lambdas, streams and files · topic 38 of 60 · 30 min

Streams: filter, map and collect

Explanation

A stream is a pipeline that processes elements from a source (a collection, an array, a file) through intermediate steps (filter, map, sorted, distinct, limit) to one terminal step (toList, count, sum, reduce, forEach). Streams are lazy and can be used only once. They describe WHAT you want, instead of writing loops.

Why streams?

Many loops follow the same shape: go through items, keep some, transform them, combine the result. The Stream API (Java 8) lets you write that as a pipeline of named steps. The code reads like the requirement: "paid orders from Pune, their amounts, total".

java
record Order(String id, String city, double amount, boolean paid) {}
List<Order> orders = List.of(
    new Order("O1", "Pune", 450, true),
    new Order("O2", "Delhi", 1200, false),
    new Order("O3", "Pune", 900, true),
    new Order("O4", "Goa", 300, true));

// Loop version
double total = 0;
for (Order o : orders) if (o.paid() && o.city().equals("Pune")) total += o.amount();

// Stream version
double total2 = orders.stream()
        .filter(Order::paid)
        .filter(o -> o.city().equals("Pune"))
        .mapToDouble(Order::amount)
        .sum();                                  // 1350.0

The three parts of a pipeline

  1. Source: list.stream(), Arrays.stream(array), Stream.of(a, b, c), IntStream.rangeClosed(1, 10), Files.lines(path).
  2. Intermediate operations (return a new stream, run lazily): filter, map, mapToInt/mapToDouble, flatMap, sorted, distinct, limit, skip, peek.
  3. One terminal operation (produces a result and runs the pipeline): toList, collect, count, sum, average, min, max, reduce, anyMatch, allMatch, findFirst, forEach.
java
List<String> topCities = orders.stream()
        .filter(Order::paid)
        .sorted(Comparator.comparingDouble(Order::amount).reversed())
        .map(Order::city)
        .distinct()
        .limit(2)
        .toList();                       // [Pune, Goa]  (toList: Java 16+, unmodifiable)

long unpaid = orders.stream().filter(o -> !o.paid()).count();             // 1
boolean anyBig = orders.stream().anyMatch(o -> o.amount() > 1000);        // true
OptionalDouble avg = orders.stream().mapToDouble(Order::amount).average(); // OptionalDouble[712.5]
int sumOfSquares = IntStream.rangeClosed(1, 5).map(n -> n * n).sum();     // 55
double product = Stream.of(2.0, 3.0, 4.0).reduce(1.0, (a, b) -> a * b);  // 24.0

Lazy and single-use

Intermediate operations do nothing until a terminal operation is called; then each element flows through the whole pipeline. That allows short-circuiting: findFirst or limit stop as soon as they have enough. A stream can be consumed only once; calling a second terminal operation on the same stream throws IllegalStateException: stream has already been operated upon or closed.

Careful: Do not use streams to change outside state: orders.stream().forEach(o -> totals.add(...)) or modifying the source list inside a stream causes bugs, especially with parallel streams. Use collect/reduce to produce results. Also, toList() returns an unmodifiable list; use collect(Collectors.toList()) or new ArrayList<>(…) if you need to add to it later.

Primitive streams

Stream<Integer> boxes every number. IntStream, LongStream and DoubleStream work on primitives and add sum, average, min, max and summaryStatistics. Switch with mapToInt/mapToDouble and back with boxed().

Think of it like this: A stream is like an assembly line in a mango-packing unit: mangoes enter at one end (source), pass a sorting station that removes damaged ones (filter), a station that grades them (map), and finally a packing station that boxes them (terminal operation). Nothing moves until the packing station starts asking for mangoes.
Tip: Keep each pipeline step simple and on its own line. If a step needs complex logic, put it in a well-named method and use a method reference.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Streams: filter, map and collect · 1/6

Streams

In this lesson you will process collections with streams.

Prefer a video by a teacher? Find free videos on “java streams tutorial filter map reduce collect” on YouTube ↗

In real life: Top 3 products by revenue

An e-commerce dashboard shows the three best-selling products of the day by revenue. A stream sorts the day's sales by revenue descending and takes three.

java
record Sale(String product, int qty, double price) { double revenue() { return qty * price; } }
List<String> top3 = sales.stream()
        .sorted(Comparator.comparingDouble(Sale::revenue).reversed())
        .limit(3)
        .map(s -> s.product() + " (Rs " + s.revenue() + ")")
        .toList();

Practice (0/3 done)

Task 1 · Question

What does this print? System.out.println(Stream.of(5, 2, 8, 2, 9).filter(n -> n > 2).distinct().sorted().toList());

Task 2 · Code · Javascript

The same pipeline in JavaScript, which runs here: write puneRevenue(orders) that returns the total amount of PAID orders from Pune, using filter, map and reduce (like Java's filter, mapToDouble and sum). Orders look like { id, city, amount, paid }.

Task 3 · Practice

Given a List<Order>(id, city, amount, paid), write stream pipelines that compute: (a) the number of unpaid orders, (b) the ids of the 2 biggest paid orders, (c) whether any order is above ₹5,000, (d) the average amount of paid orders (print "none" if there are none).

Quick quiz

Question 1

1.Which is a terminal operation?

Question 2

2.What happens if you call two terminal operations on the same stream?

Question 3

3.Stream.of(1, 2, 3).map(n -> n * 10).filter(n -> n > 10).toList() is…

Question 4

4.What does "lazy" mean for intermediate operations?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

Which is a terminal operation?

Answer: count

count produces a result and runs the pipeline.

What happens if you call two terminal operations on the same stream?

Answer: IllegalStateException

Streams are single-use.

Stream.of(1, 2, 3).map(n -> n * 10).filter(n -> n > 10).toList() is…

Answer: [20, 30]

Map to 10, 20, 30, then keep those above 10.

What does "lazy" mean for intermediate operations?

Answer: They run only when a terminal operation is called

Nothing happens until the terminal step.

Which operation turns List<Order> into Map<String, List<Order>>?Related

Answer: groupBy

groupBy collects items under each key.

From Collection operations

What happens if you remove items from a List<T> inside a foreach over the same list?Related

Answer: InvalidOperationException at run time

The list knows it was changed during enumeration and throws.

From Loops: for, while, do-while and foreach

Which stream type supports several listeners?Related

Answer: Broadcast

Broadcast streams allow multiple listeners.

From Streams

What does [3, 1, 2].map((n) => n * 2) return?Related

Answer: [6, 2, 4]

map keeps the order and doubles each item.

From Arrays and array methods

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 2 more topics in “Lambdas, streams and files” before its exam: Collectors: grouping and summarising, Reading and writing files.
  2. Module 8 exam: Lambdas, streams and files · 12 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Multithreading and concurrency. 60 topics left in the course before the final exam.

← Previous: Method references