1. What is a Gatherer?
A Gatherer is a new stream collector-like abstraction introduced in Java.
It allows you to transform a stream in ways that require maintaining state or looking at multiple elements together, beyond what map
, filter
, and flatMap
can easily do.
Think of it as a customizable intermediate stream operation, similar in power to writing your own collector, but for intermediate operations instead of terminal ones.
2. When to Use It (and When to Avoid It)
Use Gatherer when:
- You need stateful transformations (like "group consecutive elements", "batch into windows", "emit only changes").
- Standard
map
/filter
/flatMap
aren’t enough. - You want reusable and composable transformations without resorting to imperative loops.
Avoid Gatherer when:
- A simple
map
,filter
, orflatMap
does the job. - You just need a terminal reduction (
collect
,reduce
) → then use aCollector
. - Performance is critical, and you don’t need advanced stateful behavior → Gatherer adds some abstraction overhead.
3. How to Use a Gatherer
The API revolves around Gatherer.of(...)
.
A Gatherer defines how to process elements and emit zero, one, or many outputs for each input.
A Gatherer
has:
- Initializer → creates mutable state (optional).
- Integrator → processes each input element with state.
- Finisher → final step when the stream ends (optional).
Signature (simplified):
static <T, R, S> Gatherer<T, S, R> of(
Supplier<S> initializer,
Integrator<T, S, R> integrator,
BiConsumer<S, Downstream<? super R>> finisher
)
4. Examples
Example 1: Removing Consecutive Duplicates
import java.util.stream.*;
public class GathererExample1 {
public static void main(String[] args) {
var result = Stream.of("A", "A", "B", "B", "C", "A", "A")
.gather(Gatherers.distinctAdjacent()) // built-in gatherer
.toList();
System.out.println(result); // [A, B, C, A]
}
}
Gatherers.distinctAdjacent()
is a built-in gatherer that removes only consecutive duplicates.
Example 2: Batching (Windowing Elements)
import java.util.stream.*;
import java.util.*;
public class GathererExample2 {
public static void main(String[] args) {
var result = Stream.iterate(1, n -> n + 1).limit(10)
.gather(Gatherers.windowFixed(3)) // windows of size 3
.toList();
System.out.println(result);
// [[1,2,3], [4,5,6], [7,8,9], [10]]
}
}
Gatherers.windowFixed(3)
groups elements into lists of size 3
.
Example 3: Custom Gatherer — Emit Only Increasing Values
import java.util.stream.*;
import java.util.function.*;
public class GathererExample3 {
public static void main(String[] args) {
var increasingGatherer = Gatherer.of(
() -> new int[]{Integer.MIN_VALUE}, // state holder
(state, elem, downstream) -> {
if (elem > state[0]) {
state[0] = elem;
downstream.push(elem); // emit only if greater
}
return true; // keep going
}
);
var result = Stream.of(1, 2, 2, 5, 3, 7, 6, 8)
.gather(increasingGatherer)
.toList();
System.out.println(result); // [1, 2, 5, 7, 8]
}
}
Here, we wrote a custom Gatherer that emits only when the sequence increases.
5. Key Built-in Gatherers in Java
Java ships with a few ready-made gatherers under java.util.stream.Gatherers
:
-
distinctAdjacent()
→ removes consecutive duplicates. -
scanLeft(initial, op)
→ cumulative scan (likereduce
but keeps intermediates). -
windowFixed(size)
→ groups elements into fixed-size windows. -
windowSliding(size)
→ sliding windows. -
mapConcurrent(...)
→ concurrent mapping.
6. Summary
- Gatherer = Custom intermediate transformation for streams.
- Use when you need stateful or advanced element processing.
- Provides built-in gatherers (
windowFixed
,distinctAdjacent
, etc.). - Can define custom gatherers for domain-specific needs.
Top comments (0)