Appearance
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
, andDoubleStream
.