Skip to content

Creating Streams

To use Streams, you must first create them. There are many ways to create Streams, which we will introduce one by one.

Stream.of()

The simplest way to create a Stream is to use the static method Stream.of(). By passing variable arguments, you create a Stream that can output specific elements:

java
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("A", "B", "C", "D");
        // The forEach() method is equivalent to an internal loop call,
        // and you can pass a method reference that matches the Consumer interface's void accept(T t):
        stream.forEach(System.out::println);
    }
}

Although this method has essentially no substantial use, it is very convenient for testing.

Based on Arrays or Collections

The second way to create a Stream is based on an array or a Collection. In this case, the elements output by the Stream are the elements held by the array or Collection:

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

public class Main {
    public static void main(String[] args) {
        Stream<String> stream1 = Arrays.stream(new String[] { "A", "B", "C" });
        Stream<String> stream2 = List.of("X", "Y", "Z").stream();
        stream1.forEach(System.out::println);
        stream2.forEach(System.out::println);
    }
}

To convert an array into a Stream, use the Arrays.stream() method. For a Collection (such as List, Set, Queue, etc.), you can obtain a Stream by directly calling the stream() method.

The methods above for creating Streams transform an existing sequence into a Stream, with fixed elements.

Based on Supplier

Streams can also be created using the Stream.generate() method, which requires a Supplier object:

java
Stream<String> s = Stream.generate(Supplier<String> sp);

Streams created based on a Supplier continuously call the Supplier.get() method to produce the next element. These Streams do not store elements but rather store the algorithm, allowing them to represent infinite sequences.

For example, let's create a Supplier that continuously generates natural numbers. The code is straightforward: each call to the get() method generates the next natural number:

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

public class Main {
    public static void main(String[] args) {
        Stream<Integer> natural = Stream.generate(new NaturalSupplier());
        // Note: Infinite sequences must first be transformed into finite sequences before printing:
        natural.limit(20).forEach(System.out::println);
    }
}

class NaturalSupplier implements Supplier<Integer> {
    int n = 0;
    public Integer get() {
        n++;
        return n;
    }
}

In the above code, we simulate an infinite sequence using a Supplier<Integer> (of course, limited by the int range). If we were to use a List to represent this, even within the int range, it would consume enormous memory. In contrast, a Stream uses almost no space because each element is computed in real-time as needed.

For infinite sequences, directly calling terminal operations like forEach() or count() would result in an infinite loop because the sequence can never be fully computed. Therefore, the correct approach is to first transform the infinite sequence into a finite one. For example, using the limit() method can truncate the first few elements, converting it into a finite sequence. Then, calling terminal operations like forEach() or count() on this finite sequence is safe.

Other Methods

The third way to create a Stream is through interfaces provided by some APIs, which directly return a Stream.

For example, the Files class's lines() method can convert a file into a Stream, where each element represents a line of the file:

java
try (Stream<String> lines = Files.lines(Paths.get("/path/to/file.txt"))) {
    // ...
}

This method is very useful for iterating over a text file line by line.

Additionally, the Pattern class's splitAsStream() method allows you to split a long string into a Stream sequence instead of an array:

java
Pattern p = Pattern.compile("\\s+");
Stream<String> s = p.splitAsStream("The quick brown fox jumps over the lazy dog");
s.forEach(System.out::println);

Primitive Streams

Since Java's generics do not support primitive types, you cannot use Stream<int>, which would result in a compilation error. To handle int values, you must use Stream<Integer>, but this introduces frequent boxing and unboxing operations. To improve efficiency, the Java standard library provides three primitive-specific Streams: IntStream, LongStream, and DoubleStream. These Streams are used similarly to generic Streams but are designed to enhance runtime performance:

java
// Convert an int[] array to an IntStream:
IntStream is = Arrays.stream(new int[] { 1, 2, 3 });
// Convert a Stream<String> to a LongStream:
LongStream ls = List.of("1", "2", "3").stream().mapToLong(Long::parseLong);

Exercise

Write a LongStream that outputs the Fibonacci sequence:

1, 1, 2, 3, 5, 8, 13, 21, 34, ...

Summary

Methods to create Streams include:

  • Creating Streams by specifying elements, arrays, or Collections.
  • Creating Streams through a Supplier, which can represent infinite sequences.
  • Creating Streams through related methods of other classes.
  • Primitive Streams are available as IntStream, LongStream, and DoubleStream.
Creating Streams has loaded