High-performance programming with Java streams

Now consider the same example using Stream.gather() and built-in gatherers. Using stream gatherers lets us perform stateful operations directly inside the stream pipeline while keeping it lazy and readable:

List movingAverages = Stream.of(1, 2, 3, 4, 5, 6)
    .gather(Gatherers.windowSliding(3))
    .map(window -> window.stream()
        .mapToInt(Integer::intValue)
        .average()
        .orElse(0.0))
    .toList();

System.out.println(movingAverages); // [2.0, 3.0, 4.0, 5.0]

As you can see, windowSliding(3) waits until it has three elements, then emits [1,2,3] and slides forward by one: [2,3,4], [3,4,5], [4,5,6]. The gatherer manages this state automatically, so we can express complex data flows cleanly without manual buffering or loops.

Built-in gatherers

The Stream Gatherers API includes the following built-in gatherers:

  • windowFixed(n): Used for non-overlapping batches of n elements.
  • windowSliding(n): Used to create overlapping windows for moving averages or trend detection.
  • scan(seed, acc): Used for running totals or cumulative metrics.
  • mapConcurrent(maxConcurrency, mapper): Supports concurrent mapping with controlled parallelism.

Collectors vs. gatherers

In my introduction to Java streams, you learned about collectors, which serve a similar purpose to gatherers but operate differently. Collectors aggregate the entire stream into one result at the end, such as a list or sum, while gatherers operate during stream processing, maintaining context between elements. An easy way to remember the difference between the two features is that collectors finalize data once, whereas gatherers reshape it as it flows.

Donner Music, make your music with gear
Multi-Function Air Blower: Blowing, suction, extraction, and even inflation

Leave a reply

Please enter your comment!
Please enter your name here