Index

Creating Streams

Java Streams

2.1 Creating Streams from Collections

In Java, all core Collection types—such as List, Set, and Queue—provide convenient methods to create Streams: .stream() for sequential streams and .parallelStream() for parallel processing. These methods allow you to transform your existing collection data into a stream pipeline, unlocking powerful, functional-style operations.

For example, given a List of strings, you can easily create a stream and perform filtering:

List<String> names = List.of("Alice", "Bob", "Charlie");
names.stream()
     .filter(name -> name.startsWith("A"))
     .forEach(System.out::println);  // Prints: Alice

Similarly, a Set can be streamed to perform mapping operations:

Set<Integer> numbers = Set.of(1, 2, 3, 4);
numbers.stream()
       .map(n -> n * n)
       .forEach(System.out::println);  // Prints squares: 1, 4, 9, 16 (order not guaranteed)

Streams also work seamlessly with queues:

Queue<Double> queue = new LinkedList<>(List.of(1.5, 2.5, 3.5));
queue.stream()
     .filter(d -> d > 2)
     .forEach(System.out::println);  // Prints: 2.5, 3.5

Tips and Pitfalls:

By leveraging stream() and parallelStream(), you can write expressive, concise code that efficiently processes collection data.

Index

2.2 Creating Streams from Arrays

Java provides convenient ways to create streams from arrays, enabling powerful, functional-style data processing.

The most common method is Arrays.stream(), which can convert both object arrays and primitive arrays into streams. For example, given an array of Strings:

String[] fruits = {"apple", "banana", "cherry"};
Arrays.stream(fruits)
      .filter(f -> f.startsWith("b"))
      .forEach(System.out::println);
// Output: banana

For primitive arrays like int[], long[], or double[], Arrays.stream() returns specialized streams: IntStream, LongStream, and DoubleStream, respectively. These allow efficient operations without boxing overhead:

int[] numbers = {1, 2, 3, 4, 5};
int sumOfSquares = Arrays.stream(numbers)
                         .map(n -> n * n)
                         .sum();
System.out.println(sumOfSquares); // Output: 55

Another useful method is Stream.of(), which creates a stream from its arguments or an array:

Stream.of("cat", "dog", "elephant")
      .filter(s -> s.length() > 3)
      .forEach(System.out::println);
// Output: elephant

Note that for primitive arrays, Stream.of() will create a stream of the entire array as a single element, not individual elements. Therefore, prefer Arrays.stream() for primitive arrays.

These methods provide flexible entry points to stream processing from arrays, enabling concise and readable code.

Index

2.3 Creating Streams from Files and I/O

Java’s java.nio.file.Files class provides powerful methods to create streams directly from files, making it easy to process file content using the Stream API. The most commonly used method is Files.lines(Path path), which returns a Stream<String> where each element represents a line in the file.

Creating a Stream from a File

Here’s how you can read a file line-by-line as a stream:

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.io.IOException;
import java.util.stream.Stream;

public class FileStreamExample {
    public static void main(String[] args) {
        Path path = Paths.get("example.txt");

        // Using try-with-resources to ensure the stream and file are properly closed
        try (Stream<String> lines = Files.lines(path)) {
            lines.filter(line -> line.contains("Java"))
                 .map(String::toUpperCase)
                 .forEach(System.out::println);
        } catch (IOException e) {
            System.err.println("Error reading file: " + e.getMessage());
        }
    }
}

In this example, Files.lines(path) lazily reads lines from the file "example.txt". The stream is filtered to keep only lines containing "Java", converted to uppercase, and then printed.

Resource Management and Exception Handling

When working with streams from files or other I/O sources, resource management is crucial. The stream must be closed to release the underlying file handle and system resources. This is why the stream is created inside a try-with-resources statement, which ensures it closes automatically—even if exceptions occur.

Since Files.lines() can throw an IOException, proper exception handling is necessary to handle errors like missing files or permission issues gracefully.

Other I/O Stream Sources

Besides files, streams can be created from other I/O sources like:

Using streams with I/O opens up elegant and efficient ways to process data pipelines without manual iteration or buffering.

This combination of Files.lines(), try-with-resources, and Stream operations empowers concise and safe file processing in Java.

Index

2.4 Generating Streams with Stream.of(), Stream.generate(), Stream.iterate()

Java provides several factory methods for generating streams directly, without relying on collections, arrays, or files. These methods—Stream.of(), Stream.generate(), and Stream.iterate()—offer flexible ways to produce both finite and infinite streams.

Stream.of(): Static Values

Stream.of() is used to create a stream from a known set of values. It is ideal for small, fixed-size sequences:

import java.util.stream.Stream;

public class StreamOfExample {
    public static void main(String[] args) {
        Stream.of("apple", "banana", "cherry")
              .map(String::toUpperCase)
              .forEach(System.out::println);
        // Output: APPLE, BANANA, CHERRY
    }
}

Use Stream.of() when you have a predefined list of elements, not for dynamic or generated data.

Stream.generate(): Infinite Supplier-Based Streams

Stream.generate(Supplier<T>) produces an infinite stream by repeatedly calling a Supplier. To avoid infinite execution, always combine it with limit():

import java.util.stream.Stream;
import java.util.Random;

public class StreamGenerateExample {
    public static void main(String[] args) {
        Stream.generate(() -> new Random().nextInt(100))
              .limit(5)
              .forEach(System.out::println);
        // Output: 5 random integers
    }
}

This is useful for generating dynamic data like random values or timestamps.

Stream.iterate(): Sequences Based on Seed and UnaryOperator

Stream.iterate(seed, unaryOperator) creates a stream where each element is derived from the previous. It's excellent for number sequences or repeated patterns:

import java.util.stream.Stream;

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

As with generate(), use limit() to prevent infinite loops.

In summary:

These methods give developers powerful control over stream creation without relying on existing data sources.

Index