Creating Streams
Creating Streams
Before you can process data with the Streams API you need a stream. Java gives you several factory methods and source types, each suited to a different situation. Knowing which entry-point to reach for — and why — makes your code cleaner than littering it with manual loops from day one.
Why does the source matter?
A Stream<T> is a lazy, single-use pipeline. It does not hold data; it describes how data will be pulled from a source and transformed. The source determines the element type, the order guarantee, and sometimes whether the stream can run in parallel efficiently. Choosing the right factory is therefore not cosmetic — it affects correctness and performance.
Streams from Collections
The most common source is any Collection (a List, Set, or Queue). Every collection inherits stream() and parallelStream() from java.util.Collection:
List.stream() preserves insertion order. Set.stream() does not — a HashSet has no defined order. If order matters, prefer a List or LinkedHashSet.
Streams from Arrays
When your data lives in an array, use Arrays.stream(). It also accepts a range so you can stream a sub-array without copying:
Arrays.stream(int[]) returns an IntStream, not a Stream<Integer>. This is intentional — primitive streams avoid boxing overhead and are covered in depth in Lesson 8.
Stream.of — Inline Elements
Stream.of() builds a stream directly from a handful of values without creating a collection first. It is ideal for tests, quick prototypes, and times when you already have individual references:
Stream.empty() over Stream.of() with no arguments when your intent is an empty stream. The empty factory makes the meaning explicit and avoids an unchecked generic warning.
Stream.generate — Infinite Supplier-based Streams
Stream.generate(Supplier<T>) creates an infinite stream by calling a Supplier repeatedly. Because it never terminates on its own you must add a short-circuit operation such as limit():
The supplier has no memory of previous calls — each invocation is independent. If you need elements that depend on their position or on the previous value, use Stream.iterate instead.
Stream.iterate — Infinite Sequence-based Streams
Stream.iterate has two overloads:
- Two-arg (Java 8+):
Stream.iterate(seed, UnaryOperator)— infinite, terminated bylimit()ortakeWhile(). - Three-arg (Java 9+):
Stream.iterate(seed, Predicate, UnaryOperator)— built-in stop condition, works like aforloop.
collect() or count() on an unbounded generate or two-arg iterate stream will run forever (or until the JVM runs out of memory). Always pair them with limit(n) or takeWhile(predicate).
Choosing the Right Factory
- You have a
ListorSet? Use.stream(). - You have an existing array? Use
Arrays.stream(). - You have a small set of known values? Use
Stream.of(). - You need generated values with no state between calls? Use
Stream.generate(). - You need a sequence where each element depends on the previous one? Use
Stream.iterate().
Summary
Java provides five primary ways to create a stream: from a Collection, from an array via Arrays.stream(), from literal values via Stream.of(), and from infinite sources via Stream.generate() and Stream.iterate(). Each source type has a clear use-case. In the next lesson you will attach the first transformation stages — filter, map, and forEach — to the streams you create here.