Stream Pipelines and Chaining Operations

In Java 8 Streams, a stream pipeline is a sequence of operations that process a stream of elements in a functional and declarative manner. It consists of a source, zero or more intermediate operations, and a terminal operation. Chaining operations refers to the process of combining multiple intermediate operations in a fluent API style to transform and process data efficiently.

Stream Pipeline Overview

 stream pipeline processes data in three stages:

  1. Source: The origin of the data (e.g., a collection, array, or generator).
  2. Intermediate Operations: Transformations or filters applied to the stream (e.g., filter, map, sorted). These are lazy and produce a new stream.
  3. Terminal Operation: A final operation that produces a result or side effect (e.g., collect, forEach, reduce). This triggers the execution of the pipeline.

Key Characteristics

  • Fluent API: Operations are chained using method calls (e.g., stream.filter(…).map(…).collect(…)), making the code concise and readable.
  • Lazy Evaluation: Intermediate operations are not executed until a terminal operation is invoked.
  • Single Pass: The pipeline processes elements in one pass, optimizing performance.
  • Immutability: Streams do not modify the source data; they produce new results.

Chaining Operations

Chaining operations involves linking multiple intermediate operations to transform the stream progressively. Each intermediate operation returns a new Stream, allowing further operations to be appended.

Common Intermediate Operations

  • filter(Predicate<T>): Keeps elements matching the predicate.
  • map(Function<T, R>): Transforms each element into a new value.
  • flatMap(Function<T, Stream<R>>): Flattens nested streams into a single stream.
  • sorted() or sorted(Comparator<T>): Sorts elements.
  • distinct(): Removes duplicates.
  • limit(long maxSize): Restricts the number of elements.
  • skip(long n): Skips the first n elements.
  • peek(Consumer<T>): Performs an action on each element (useful for debugging).

Common Terminal Operations

  • collect(Collector): Accumulates elements into a collection (e.g., toList(), groupingBy).
  • forEach(Consumer): Applies an action to each element.
  • reduce(BinaryOperator): Combines elements into a single result.
  • count(): Returns the number of elements.
  • findFirst() or findAny(): Returns an element (or Optional).
  • anyMatch(Predicate), allMatch(Predicate), noneMatch(Predicate): Checks conditions.

How Stream Pipelines Work

  1. Source Creation: A stream is created from a source (e.g., List.stream(), Stream.of(), Arrays.stream()).
  2. Chaining Intermediate Operations: Operations like filter, map, etc., are chained to define the pipeline. These are lazy and build a pipeline definition without processing data.
  3. Terminal Operation: When a terminal operation is called, the pipeline is executed, processing elements from the source through all intermediate operations in one pass.

Examples of Stream Pipelines and Chaining

1. Basic Pipeline: Filter and Collect

Filter even numbers from a list and collect them into a new list:

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

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
        List<Integer> evenNumbers = numbers.stream()
                .filter(n -> n % 2 == 0)
                .collect(Collectors.toList());

        System.out.println("Even numbers: " + evenNumbers);
    }
}

/*
Even numbers: [2, 4, 6]
*/

2. Chaining Multiple Operations

Filter even numbers, double them, and sort in descending order:

List<Integer> numbers = Arrays.asList(1, 4, 2, 5, 3, 6);
List<Integer> result = numbers.stream()
        .filter(n -> n % 2 == 0)
        .map(n -> n * 2)
        .sorted(Comparator.reverseOrder())
        .collect(Collectors.toList());

System.out.println("Processed numbers: " + result);

/*
Processed numbers: [12, 8, 4]
*/Code language: PHP (php)

3. Pipeline with FlatMap

Flatten a list of lists and filter elements:

List<List<String>> listOfLists = Arrays.asList(
        Arrays.asList("apple", "banana"),
        Arrays.asList("cherry", "date"),
        Arrays.asList("elderberry")
);
List<String> longWords = listOfLists.stream()
        .flatMap(List::stream)
        .filter(s -> s.length() > 5)
        .collect(Collectors.toList());

System.out.println("Words longer than 5 characters: " + longWords);

/*
Words longer than 5 characters: [banana, cherry, elderberry]
*/Code language: PHP (php)

4. Pipeline with Short-Circuiting

Find the first number greater than 3:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> first = numbers.stream()
        .filter(n -> {
            System.out.println("Filtering: " + n);
            return n > 3;
        })
        .map(n -> {
            System.out.println("Mapping: " + n);
            return n * 2;
        })
        .findFirst();

System.out.println("First number > 3 (doubled): " + first.orElse(null));
/*
Filtering: 1
Filtering: 2
Filtering: 3
Filtering: 4
Mapping: 4
First number > 3 (doubled): 8
*/Code language: PHP (php)

5. Pipeline with Aggregation

Group words by their length:

List<String> words = Arrays.asList("cat", "dog", "elephant", "rat", "rhinoceros");
Map<Integer, List<String>> wordsByLength = words.stream()
        .filter(s -> s.length() > 2)
        .collect(Collectors.groupingBy(String::length));

System.out.println("Words grouped by length: " + wordsByLength);
/*
Words grouped by length: {3=[cat, dog, rat], 8=[elephant], 10=[rhinoceros]}
*/
Code language: JavaScript (javascript)

Example: Complex Pipeline

Process a list of employees to find the names of high-salary employees in a specific department, sorted alphabetically:

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

class Employee {
    String name;
    String department;
    double salary;

    Employee(String name, String department, double salary) {
        this.name = name;
        this.department = department;
        this.salary = salary;
    }

    public String getName() { return name; }
    public String getDepartment() { return department; }
    public double getSalary() { return salary; }
}

public class Main {
    public static void main(String[] args) {
        List<Employee> employees = Arrays.asList(
                new Employee("Aravind", "HR", 60000),
                new Employee("Bharat", "IT", 80000),
                new Employee("Charan", "HR", 75000),
                new Employee("Dharanish", "IT", 90000)
        );

        List<String> highSalaryITNames = employees.stream()
                .filter(e -> e.getDepartment().equals("IT"))
                .filter(e -> e.getSalary() > 70000)
                .map(Employee::getName)
                .sorted()
                .collect(Collectors.toList());

        System.out.println("High-salary IT employees: " + highSalaryITNames);
    }
}

/*
High-salary IT employees: [Bharat, Dharanish]

*/
Scroll to Top