toList
Materializes a lazy iterable — pulling every value and collecting them into a List.
Lecture
toList is the workhorse terminal operator:
it's what actually runs a chain that was, until this point, nothing but
a plan. Calling .toList() pulls every remaining value out
of the iterable, in order, and collects them into a real
List<T>. Everything upstream — every .map(),
.filter(), .take() — only runs because
toList asked for values.
That also means toList is exactly the operator you must
not call directly on an infinite or unbounded source
(range with no end, cycle, repeat
with a huge count) — it will try to pull forever. Bound it first with
take(n), then call toList on the bounded
result.
The async version, toListAsync (or .toList()
on an FxAsync chain), awaits each element as it's pulled
and returns a Future<List<T>>. Combined with
.concurrent(n) upstream, the individual awaits can overlap
even though the final list still comes back in the original order.
Demo 1 · Basics & laziness
Note how map only actually runs for the 3 values that
take(3) allows through — out of a million:
Demo 2 · Async, with concurrency
.toList() on an FxAsync chain awaits every
element and hands back the whole list at once; add
.concurrent(n) upstream to overlap the individual waits:
Try it yourself
Exercise: materialize the first 4 squares of a huge range into a List.
each — terminal op for side effects instead of a List ·
consume — terminal op that discards results entirely ·
fx — the chain that toList terminates ·
concurrent — parallel evaluation