Module: Stream API

Creating Streams

Java Core: Stream API - Creating Streams

The Stream API, introduced in Java 8, provides a functional and declarative way to process collections of data. A core concept is the Stream itself. This document details how to create streams from various sources.

What is a Stream?

A Stream is a sequence of elements supporting sequential and parallel aggregate operations. Key characteristics:

  • Not a data structure: It doesn't store data; it operates on a source.
  • Functional: Encourages operations like map, filter, reduce without side effects.
  • Lazy: Operations are only performed when a terminal operation is invoked.
  • Immutable: Streams themselves are not modified. Operations create new streams.

Ways to Create Streams

Here's a breakdown of common methods for creating streams:

1. From Collections:

The most frequent way to create streams is from existing collections like List, Set, and Map.

  • Collection.stream(): Creates a sequential stream.
  • Collection.parallelStream(): Creates a parallel stream (for potential performance gains on multi-core processors).
import java.util.List;
import java.util.Arrays;

public class StreamFromCollection {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

        // Sequential stream
        names.stream().forEach(System.out::println);

        // Parallel stream
        names.parallelStream().forEach(System.out::println);
    }
}

2. From Arrays:

Similar to collections, you can create streams from arrays.

  • Arrays.stream(array): Creates a sequential stream from an array.
  • Arrays.stream(array).parallel(): Creates a parallel stream from an array.
import java.util.Arrays;

public class StreamFromArray {
    public static void main(String[] args) {
        int[] numbers = {1, 2, 3, 4, 5};

        // Sequential stream
        Arrays.stream(numbers).forEach(System.out::println);

        // Parallel stream
        Arrays.stream(numbers).parallel().forEach(System.out::println);
    }
}

3. Using Stream.of():

This static method allows you to create streams from individual elements.

  • Stream.of(element1, element2, ...): Creates a sequential stream from a variable number of arguments.
import java.util.stream.Stream;

public class StreamOf {
    public static void main(String[] args) {
        Stream<String> colors = Stream.of("Red", "Green", "Blue");
        colors.forEach(System.out::println);
    }
}

4. Using Stream.iterate():

Creates an infinite sequential stream by repeatedly applying a function to an initial seed value. Requires a terminal operation to limit the stream.

  • Stream.iterate(seed, function): seed is the initial value, and function takes the previous value and returns the next.
import java.util.stream.Stream;

public class StreamIterate {
    public static void main(String[] args) {
        Stream<Integer> evenNumbers = Stream.iterate(0, n -> n + 2).limit(5);
        evenNumbers.forEach(System.out::println); // Output: 0 2 4 6 8
    }
}

5. Using Stream.generate():

Creates an infinite sequential stream by repeatedly calling a supplier. Requires a terminal operation to limit the stream.

  • Stream.generate(supplier): supplier provides the next element in the stream.
import java.util.stream.Stream;
import java.util.Random;

public class StreamGenerate {
    public static void main(String[] args) {
        Random random = new Random();
        Stream<Integer> randomNumbers = Stream.generate(random::nextInt).limit(5);
        randomNumbers.forEach(System.out::println);
    }
}

6. From BufferedReader (Java 9+):

Java 9 introduced methods to create streams directly from BufferedReader instances.

  • BufferedReader.lines(): Creates a stream of lines from the reader.
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class StreamFromBufferedReader {
    public static void main(String[] args) throws IOException {
        try (BufferedReader reader = new BufferedReader(new FileReader("myFile.txt"))) {
            reader.lines().forEach(System.out::println);
        }
    }
}

7. From IntStream, LongStream, DoubleStream:

These specialized stream types are designed for primitive int, long, and double values, respectively. They offer performance benefits and specific methods for primitive operations. Creation methods are similar to Stream.of(), Stream.iterate(), and Stream.generate().

import java.util.stream.IntStream;

public class PrimitiveStreams {
    public static void main(String[] args) {
        IntStream intStream = IntStream.range(1, 6); // 1 to 5 (exclusive of 6)
        intStream.forEach(System.out::println);
    }
}

Choosing the Right Method

  • Existing Collections/Arrays: Use collection.stream() or Arrays.stream().
  • Fixed Set of Elements: Use Stream.of().
  • Infinite Sequences: Use Stream.iterate() or Stream.generate(), always with a terminal operation and a limit() to prevent infinite loops.
  • File Input: Use BufferedReader.lines() (Java 9+).
  • Primitive Types: Use IntStream, LongStream, or DoubleStream for performance.

Remember to always consider whether a sequential or parallel stream is appropriate for your use case. Parallel streams can improve performance for computationally intensive tasks, but they also introduce overhead. Careful benchmarking is often necessary to determine the optimal approach.