Chapter 21: Stream API (Java 8+)

The Stream API lets you process collections (lists, sets, arrays, etc.) in a declarative style — you tell what you want to do, not how to do it step-by-step. It’s like giving instructions to a smart assistant instead of doing everything manually.

Imagine we’re sitting together in a quiet Bandra café — it’s evening, the sea is calm outside, and I’m going to explain Streams like I’m teaching my younger brother who just discovered how much easier life becomes after learning them.

We’ll go super slowly, with real-life analogies, complete runnable programs, step-by-step breakdowns, tables, common mistakes with fixes, and tons of examples you can copy-paste and run right now.

Let’s dive in!

1. What is a Stream? (The Big Idea)

A Stream is not a data structure — it’s a pipeline for processing a sequence of elements (from a Collection, array, or generated data) in a functional style.

Key Characteristics:

  • Not storing data — just processing data from a source
  • Lazy evaluation — intermediate operations are not executed until a terminal operation is called
  • Once consumed — a stream can be used only once (like a river: once water flows, you can’t use it again)
  • Functional & immutable — operations return new streams (original data unchanged)
  • Parallelizable — easy to make multi-threaded

Real-life analogy: Think of a factory assembly line:

  • Source = raw materials (Collection)
  • Intermediate operations = machines that transform (filter, map, sort…)
  • Terminal operation = final product (collect to list, count, print…)

2. Creating Streams (Different Ways)

Source How to Create Stream Example
Collection collection.stream() list.stream()
Array Arrays.stream(array) or Stream.of(array) Arrays.stream(numbers)
Static factory methods Stream.of(…), Stream.iterate(), Stream.generate() Stream.of(1,2,3)
Files / Lines Files.lines(path) Files.lines(Paths.get(“file.txt”))
Infinite streams Stream.iterate(seed, function) Stream.iterate(0, n -> n + 2) (even numbers)

Example 1: Different Ways to Create Streams

Java

Output:

text

3. Intermediate & Terminal Operations

Streams have two kinds of operations:

Type Description Returns Lazy? Examples
Intermediate Transform the stream, return new stream Stream Yes filter(), map(), sorted(), distinct(), limit(), skip()
Terminal Produce a result or side-effect, close the stream Non-Stream (List, int, void…) No forEach(), collect(), reduce(), count(), anyMatch(), findFirst()

Important: Intermediate operations are lazy — nothing happens until a terminal operation is called!

Example 2: Full Stream Pipeline

Java

Output:

text

Step-by-step:

  1. filter() → only names > 5 chars: Webliance, Priya, Rahul, Sneha, Vikram, Ananya
  2. map() → uppercase: WEBLIANCE, PRIYA, RAHUL, SNEHA, VIKRAM, ANANYA
  3. sorted() → ANANYA, PRIYA, RAHUL, SNEHA, VIKRAM, WEBLIANCE
  4. limit(3) → ANANYA, PRIYA, WEBLIANCE
  5. collect() → terminal → produces the List

4. Common Intermediate Operations

Operation Description Example
filter(Predicate) Keep elements that match condition .filter(n -> n % 2 == 0)
map(Function) Transform each element .map(String::toUpperCase)
flatMap(Function) Flatten nested structures .flatMap(list -> list.stream())
distinct() Remove duplicates .distinct()
sorted() Sort (natural or Comparator) .sorted() or .sorted(Comparator.reverseOrder())
limit(n) Take first n elements .limit(5)
skip(n) Skip first n elements .skip(2)

5. Common Terminal Operations

Operation Description Return Type
forEach(Consumer) Perform action on each element void
collect(Collector) Collect into List/Set/Map/String… List, Set, Map, String…
reduce() Combine elements into single result Optional<T>
count() Number of elements long
anyMatch(Predicate) Any element matches? boolean
allMatch(), noneMatch() All / none match boolean
findFirst(), findAny() First / any element Optional<T>

Collectors (most powerful):

Java

6. Parallel Streams (Easy Multi-threading)

Just add .parallel() — Java splits the work across multiple threads!

Example:

Java

When to use parallel streams?

  • Large datasets (10k+ elements)
  • Expensive operations (filter/map)
  • Not for small lists or I/O-bound tasks

Caution: Not all operations are thread-safe — be careful with stateful operations.

Quick Recap Table (Your Cheat Sheet)

Concept Key Points / Example Benefit
Creating Stream list.stream(), Stream.of(), Arrays.stream() Source for pipeline
Intermediate filter(), map(), sorted(), limit() Transform data (lazy)
Terminal collect(), forEach(), reduce(), count() Produce result / side-effect
Parallel Stream .parallelStream() or .parallel() Automatic multi-threading
Collectors toList(), joining(), groupingBy() Powerful way to collect results

Common Mistakes & Fixes

Mistake Problem Fix
Using stream after terminal operation IllegalStateException: stream has already been operated Create new stream each time
Modifying collection inside forEach ConcurrentModificationException Collect to new list or use parallel carefully
Using parallel on small collections Overhead > benefit Use sequential for <10k elements
Forgetting to import Collectors Cannot find symbol import java.util.stream.Collectors;
Chaining too many operations Hard to read Break into smaller methods

Homework for You (Practice to Master!)

  1. Basic: From a list of strings, filter names starting with “A”, convert to uppercase, sort, and collect to List.
  2. Medium: From list of integers 1 to 100, find sum of all even numbers using filter + mapToInt + sum().
  3. Advanced: Group a list of strings by their length using Collectors.groupingBy().
  4. Fun: Generate 10 random numbers using Stream.generate(Math::random).limit(10) and print them.
  5. Challenge: Find the first name longer than 6 characters from a list (use findFirst()).

You’re doing fantastic! Stream API + lambdas is the modern way to process data in Java — now your code looks clean, concise, and professional.

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *