Java Stream API

The Stream API, introduced in Java 8, provides a powerful and expressive way to process sequences of data — such as collections or arrays — using a pipeline of operations. Streams make it possible to perform complex data processing (filtering, transforming, sorting, aggregating) in a clean, readable, and often single-expression style.

What is a Stream?

A stream is not a data structure — it does not store data. It is a pipeline through which data flows and is processed. Think of it like an assembly line in a factory — raw materials go in one end, pass through various processing stations, and a finished product comes out the other.

  • Streams do not modify the original collection.
  • A stream can only be consumed once.
  • Streams support both sequential and parallel processing.

Stream Pipeline

A Stream pipeline consists of three parts:

  1. Source: The data source (Collection, Array, or generated sequence).
  2. Intermediate Operations: Transform or filter the stream. They are lazy — they do not run until a terminal operation is called.
  3. Terminal Operation: Produces a result or side effect and ends the pipeline.
collection.stream()               // Source
    .filter(...)                  // Intermediate
    .map(...)                     // Intermediate
    .collect(Collectors.toList()) // Terminal

Creating a Stream

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

// From a List
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Stream<String> stream1 = names.stream();

// From an Array
String[] arr = {"Java", "Python", "C++"};
Stream<String> stream2 = Arrays.stream(arr);

// Using Stream.of()
Stream<Integer> stream3 = Stream.of(1, 2, 3, 4, 5);

Intermediate Operations

filter() – Keep elements that match a condition

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

numbers.stream()
    .filter(n -> n % 2 == 0)
    .forEach(System.out::println);   // 2 4 6 8 10

map() – Transform each element

List<String> names = Arrays.asList("alice", "bob", "charlie");

names.stream()
    .map(String::toUpperCase)
    .forEach(System.out::println);   // ALICE  BOB  CHARLIE

sorted() – Sort elements

List<Integer> nums = Arrays.asList(5, 1, 8, 3, 7);

nums.stream()
    .sorted()
    .forEach(n -> System.out.print(n + " "));   // 1 3 5 7 8

distinct() – Remove duplicates

Arrays.asList(1, 2, 2, 3, 3, 3, 4)
    .stream()
    .distinct()
    .forEach(n -> System.out.print(n + " "));   // 1 2 3 4

limit() and skip()

// limit: take first N elements
Stream.iterate(1, n -> n + 1)
    .limit(5)
    .forEach(n -> System.out.print(n + " "));   // 1 2 3 4 5

// skip: skip first N elements
Arrays.asList(10, 20, 30, 40, 50)
    .stream()
    .skip(2)
    .forEach(n -> System.out.print(n + " "));   // 30 40 50

peek() – View elements without modifying (useful for debugging)

Arrays.asList(1, 2, 3, 4, 5)
    .stream()
    .peek(n -> System.out.print("Before: " + n + " | "))
    .filter(n -> n > 2)
    .forEach(n -> System.out.println("After: " + n));

Terminal Operations

forEach() – Perform an action on each element

List.of("Java", "is", "awesome")
    .stream()
    .forEach(System.out::println);

collect() – Gather stream results into a collection

import java.util.stream.Collectors;

List<String> filtered = names.stream()
    .filter(n -> n.length() > 3)
    .collect(Collectors.toList());

System.out.println(filtered);

count() – Count elements

long count = Arrays.asList(10, 25, 30, 45, 60)
    .stream()
    .filter(n -> n > 20)
    .count();

System.out.println("Count: " + count);   // 4

reduce() – Combine elements into one result

int sum = Arrays.asList(1, 2, 3, 4, 5)
    .stream()
    .reduce(0, Integer::sum);

System.out.println("Sum: " + sum);   // 15

min() and max()

Optional<Integer> max = Arrays.asList(3, 1, 9, 5, 7)
    .stream()
    .max(Integer::compareTo);

System.out.println("Max: " + max.get());   // 9

anyMatch(), allMatch(), noneMatch()

List<Integer> numbers = Arrays.asList(2, 4, 6, 8, 10);

System.out.println(numbers.stream().anyMatch(n -> n > 5));    // true
System.out.println(numbers.stream().allMatch(n -> n % 2 == 0)); // true
System.out.println(numbers.stream().noneMatch(n -> n < 0));   // true

Complete Example – Student Report Processing

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

public class StreamExample {
    public static void main(String[] args) {
        List<String> students = Arrays.asList(
            "Alice:85", "Bob:72", "Charlie:91", "Diana:60", "Eve:88"
        );

        // Step 1: Parse scores and filter passing students (marks >= 75)
        // Step 2: Sort by score descending
        // Step 3: Print top students

        students.stream()
            .map(s -> s.split(":"))
            .filter(parts -> Integer.parseInt(parts[1]) >= 75)
            .sorted((a, b) -> Integer.parseInt(b[1]) - Integer.parseInt(a[1]))
            .forEach(parts -> System.out.println(parts[0] + " – " + parts[1]));
    }
}

Output:

Charlie – 91
Eve – 88
Alice – 85

Parallel Streams

For large datasets, streams can be processed in parallel using parallelStream(), which utilizes multiple CPU cores automatically.

List<Integer> largeList = // ... large dataset
largeList.parallelStream()
    .filter(n -> n % 2 == 0)
    .forEach(System.out::println);

Summary

  • Stream API processes sequences of data through a pipeline: source → intermediate operations → terminal operation.
  • Streams do not store data — they process it lazily.
  • Key intermediate operations: filter(), map(), sorted(), distinct(), limit(), skip().
  • Key terminal operations: forEach(), collect(), count(), reduce(), min(), max().
  • Streams greatly reduce the amount of code needed for complex data transformations.
  • parallelStream() enables multi-core processing for large data sets.

Leave a Comment

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