Stream Gatherers practice questions

From OCP Java SE 25 (1Z0-831) · 18 questions on this topic

Stream Gatherers practice questions from OCP Java SE 25 (1Z0-831). This pack has 18 questions tagged Stream Gatherers, drawn from its timed mock exams. 8 of them are worked through in full below — the question, every option, why each is right or wrong, and the explanation.

Worked examples for Stream Gatherers

  1. Question 1

    Two built-in gatherers are composed with Gatherer.andThen: the stream is first windowed, and the windows are then fed to a scan. What does this program print? ```java import java.util.List; import java.util.stream.Gatherers; import java.util.stream.Stream; public class Main { public static void main(String[] args) { List<Integer> out = Stream.of(1, 2, 3, 4, 5, 6) .gather(Gatherers.<Integer>windowFixed(2) .andThen(Gatherers.scan(() -> 0, (acc, w) -> acc + w.get(1)))) .toList(); System.out.println(out); } } ```

    1. A. [2, 6, 12]Correct answer

      windowFixed emits the three pairs, and scan adds each window's second element to a running state seeded at zero, emitting after each step: 2, then 6, then 12.

    2. B. [0, 2, 6, 12]

      Expects scan to emit its seed first; scan pushes state only after each integration, so the initial zero is never output.

    3. C. [3, 10, 21]

      Sums both elements of each window instead of only the second.

    4. D. [2, 4, 6]

      Forgets that the state accumulates across windows; each output is the running total, not the individual window's second element.

    Explanation

    Gatherer.andThen fuses the two gatherers so each fixed window becomes one input to the scan. scan keeps a running state from the initializer and emits the state after each element is integrated, never emitting the seed by itself. The running total therefore carries across successive windows.

  2. Question 2

    What does this print for an empty source stream? ```java import java.util.stream.*; import static java.util.stream.Gatherers.*; public class Main { public static void main(String[] args) { System.out.println(Stream.<Integer>of().gather(fold(() -> 0, Integer::sum)).toList()); } } ```

    1. A. []

      Assumes an empty source yields no output; that is scan's behaviour — fold always emits exactly one element, even from an empty stream.

    2. B. 0

      Forgets that fold is an intermediate gatherer producing a stream, not a terminal reduction; the value is wrapped in a list and printed as [0].

    3. C. Throws NoSuchElementException

      An empty stream never throws here; fold has an initial value to fall back on, so nothing is missing to trigger NoSuchElementException.

    4. D. [0]Correct answer

      fold seeds an accumulator with the initial value and emits that single final value even with nothing to combine, so an empty source still produces the seed 0 wrapped in a one-element list.

    Explanation

    fold seeds an accumulator with the supplied initial value, combines every element into it, and emits exactly one final value downstream — even when the source is empty, in which case the untouched seed is emitted. Because fold is an intermediate gatherer it yields a stream, so the lone value appears wrapped in the collected list. This is the fold-versus-scan empty-source pair: fold on empty emits the seed, scan on empty emits nothing.

  3. Question 3

    The integrator pushes each element, then returns `e < 3`. What does this print? ```java import java.util.stream.*; public class Main { public static void main(String[] args) { Gatherer<Integer,?,Integer> g = Gatherer.of((state, e, downstream) -> { downstream.push(e); return e < 3; }); var out = Stream.of(1,2,3,4,5) .peek(x -> System.out.print(x)) .gather(g) .toList(); System.out.println("=" + out); } } ```

    1. A. 12345=[1, 2, 3, 4, 5]

      Assumes the integrator's boolean return is ignored and every element flows; returning false actually stops upstream from being pulled.

    2. B. 123=[1, 2, 3]Correct answer

      The integrator pushes each element before returning e < 3; on element 3 it pushes 3 then returns false, short-circuiting so 4 and 5 are never pulled. The already-pushed 1, 2, 3 form both the printed side effect and the list.

    3. C. 123=[1, 2]

      Assumes the element that triggered the stop (3) is dropped; but push(e) already ran before the false return, so 3 is included.

    4. D. 1234=[1, 2, 3]

      Assumes one extra element is pulled after the stop; short-circuiting takes effect immediately, so 4 is never requested and peek never prints it.

    Explanation

    A custom Gatherer's integrator returns a boolean: true means keep feeding elements, false means it is done and upstream stops being pulled. Because the integrator pushes an element before it returns false, whatever was pushed is still delivered downstream; the boolean governs only future elements, not the current one. Once the integrator signals completion the remaining source elements are never requested, so a peek on the source never observes them.

  4. Question 4

    What does this print? ```java import java.util.stream.*; import static java.util.stream.Gatherers.*; public class Main { public static void main(String[] args) { System.out.println(Stream.of(1, 2, 3, 4).gather(windowSliding(2)).toList()); } } ```

    1. A. [[1, 2], [2, 3], [3, 4]]Correct answer

      Correct: windowSliding(2) starts a window at each element, advancing by one, and emits only full-size windows, giving the three overlapping pairs [1,2], [2,3], [3,4] (4 cannot start a full pair).

    2. B. [[1, 2], [3, 4]]

      That is windowFixed(2) output; it encodes the misconception that sliding windows are non-overlapping, but sliding windows overlap by design, sharing all but one element.

    3. C. [[1, 2], [2, 3], [3, 4], [4]]

      Assumes a short trailing window is emitted once the source runs out; that is windowFixed behaviour, whereas windowSliding never emits a partial window when the source is longer than the window size.

    4. D. [[1], [1, 2], [2, 3], [3, 4]]

      Assumes the windows ramp up from size 1 as the buffer fills, emitting prefixes; no warm-up windows are produced — nothing is pushed until the first full window exists.

    Explanation

    Trace: `windowSliding(n)` emits a window starting at every element, advancing the start by exactly one each time, and it emits ONLY full-size windows. With 4 elements and a window size of 2 that is 4 - 2 + 1 = 3 windows: the window starting at 1 is `[1, 2]`, the one starting at 2 is `[2, 3]`, the one starting at 3 is `[3, 4]`. The element 4 cannot start a full window of 2, so no window begins there. That gives `[[1, 2], [2, 3], [3, 4]]`. Why the others are wrong: `[[1, 2], [3, 4]]` is what `windowFixed(2)` produces — it encodes the misconception that sliding windows are non-overlapping, i.e. that `windowSliding` and `windowFixed` differ only in name. Sliding windows overlap by design; consecutive windows share all but one element. `[[1, 2], [2, 3], [3, 4], [4]]` encodes the belief that a short trailing window is emitted once the source runs out — that is `windowFixed` behaviour (it keeps a short final window), but `windowSliding` never emits a partial window when the source is longer than the window size. `[[1], [1, 2], [2, 3], [3, 4]]` encodes the belief that the windows ramp up from size 1 as the buffer fills, emitting prefixes before the first full window. No warm-up windows are emitted; nothing is pushed until the first full window exists. Exam tip: `windowFixed(n)` partitions (disjoint, keeps a short tail); `windowSliding(n)` slides by one (overlapping, all windows exactly size n). The one exception to "always full size" is a source SHORTER than the window: `windowSliding(3)` over two elements emits `[[1, 2]]`, a single short window, rather than nothing. Both gatherers hand you unmodifiable windows.

  5. Question 5

    The integrator pushes two values per element and always returns true, ignoring whatever push returns. A limit is applied downstream of the gather, and a peek upstream of it prints each element the source actually delivers. What does this print? ```java import java.util.stream.*; public class Main { public static void main(String[] args) { Gatherer<Integer, ?, Integer> g = Gatherer.of((state, e, downstream) -> { downstream.push(e); downstream.push(e * 10); return true; }); var out = Stream.of(1, 2, 3, 4) .peek(x -> System.out.print("s" + x)) .gather(g) .limit(3) .toList(); System.out.println(" -> " + out); } } ```

    1. A. s1s2s3 -> [1, 10, 2]

      Assumes the integrator returning true forces the pipeline to hand it one more element; true only means "I am willing to accept more", and the pipeline still finds the downstream rejecting, so element 3 is never pulled.

    2. B. s1s2 -> [1, 10, 2]Correct answer

      Correct: limit(3) accepts 1, 10 and 2 then rejects the fourth push (20); the integrator ignores that false return, but the rejecting downstream stops the source from being pulled, so peek prints only s1s2.

    3. C. s1s2s3s4 -> [1, 10, 2]

      Assumes limit is a post-hoc trim of a finished result while the source drains end to end; limit short-circuits, propagating a rejecting downstream back up so the source stops being pulled.

    4. D. s1s2 -> [1, 10, 2, 20]

      Assumes a single integrator call is atomic, so all its pushes must be delivered and limit cannot cut mid-call; rejection is checked per push, not per element, and a limit never overshoots its count, so 20 is dropped.

    Explanation

    Trace: `limit(3)` sits downstream of the gatherer and will accept exactly three values. Element 1 arrives (peek prints `s1`); the integrator pushes 1 (accepted, 1 of 3) and pushes 10 (accepted, 2 of 3). Element 2 arrives (peek prints `s2`); the integrator pushes 2 (accepted, 3 of 3 — the limit is now satisfied) and then pushes 20, which the downstream REJECTS. `push` returns false to say so, but this integrator ignores the return value and returns `true`. That does not resurrect the pipeline: the value 20 is simply discarded, and because the downstream is now in a rejecting state the pipeline stops pulling from the source. Elements 3 and 4 are never delivered, so peek prints nothing more. Output: `s1s2 -> [1, 10, 2]`. Why the others are wrong: `s1s2 -> [1, 10, 2, 20]` encodes the belief that a single integrator call is atomic — that once the integrator starts, all of its pushes must be delivered, so `limit(3)` cannot cut in the middle of a call and ends up with four elements. Rejection is checked per push, not per element, and a limit never overshoots its count. `s1s2s3s4 -> [1, 10, 2]` encodes the belief that `limit` is a post-hoc trim of the finished result while the source is still drained end to end. Limit short-circuits: it propagates a rejecting downstream back up, and the source stops being pulled. `s1s2s3 -> [1, 10, 2]` encodes the belief that the integrator returning `true` forces the pipeline to hand it one more element before it notices the limit is full. The integrator's `true` only means "I am willing to accept more"; the pipeline still checks whether the downstream will take anything, and it will not. Exam tip: there are two independent brakes on a gather. The integrator's boolean return is the gatherer saying "stop feeding ME"; `Downstream.push` returning `false` is the downstream saying "stop feeding THEM". A well-behaved integrator forwards the second brake by returning `push(...)`'s own result — writing `return downstream.push(e);` instead of pushing and returning a bare `true`. Ignoring it, as here, is not a crash: surplus pushes are dropped silently and the stream still terminates, which is exactly what makes the bug hard to spot.

  6. Question 6

    What does this print? ```java import java.util.stream.*; import static java.util.stream.Gatherers.*; public class Main { public static void main(String[] args) { System.out.println(Stream.of(1, 2, 3, 4).gather(fold(() -> 100, (a, b) -> a + b)).toList()); } } ```

    1. A. [110]Correct answer

      fold is a many-to-one gatherer: it starts from the initializer value 100, folds every element in (100+1+2+3+4 = 110), and emits only that single accumulated value when the source is exhausted; as an intermediate op it returns a stream, so toList() wraps it as [110].

    2. B. 110

      Believes gather(fold(...)) is a terminal reduction yielding the number itself like Stream.reduce; a gatherer is always an intermediate operation returning a Stream, so toList() is still needed and the printed form has brackets.

    3. C. [100, 101, 103, 106, 110]

      The classic fold/scan confusion — emitting the seed and then every running total; that is what scan does (and scan here would give [101, 103, 106, 110]), whereas fold emits one value for the whole stream.

    4. D. [10]

      Treats the initializer as an identity of zero (1+2+3+4 = 10); the seed 100 is a real starting value that participates in the arithmetic.

    Explanation

    Trace: `fold` is a many-to-ONE gatherer. It starts from the value the initializer supplies (100 here), folds every element into it (100+1=101, +2=103, +3=106, +4=110) and emits nothing at all until the source is exhausted — then its finisher pushes the single accumulated value downstream. The result is therefore a stream of exactly one element, and `toList()` wraps it: `[110]`. Why the others are wrong: `[10]` is 1+2+3+4 with the initializer treated as an identity of zero — it encodes the belief that `fold`'s initializer is just a type witness rather than a real starting value that participates in the arithmetic. The seed is genuinely folded in. `[100, 101, 103, 106, 110]` encodes the classic fold/scan confusion: it is what you would expect if the gatherer emitted the seed and then every running total. That is what `scan` is for; `scan` emits one value per input element (and does NOT emit the bare seed, so even `scan` here would give `[101, 103, 106, 110]`, not this). `110` encodes the belief that `gather(fold(...))` is a terminal reduction that yields the number itself, like `Stream.reduce`. A gatherer is always an INTERMEDIATE operation: it returns a Stream, so `toList()` is still needed and the printed form still has brackets. Exam tip: `fold` = one output for the whole stream; `scan` = one output per element. The bracket in `[110]` is the whole tell that a gatherer, unlike `reduce`, never leaves the stream pipeline. A useful corollary the exam likes: because `fold`'s finisher always pushes, folding an EMPTY stream still emits the seed — `Stream.<Integer>of().gather(fold(() -> 0, Integer::sum)).toList()` is `[0]`, not `[]`.

  7. Question 7

    An infinite stream is windowed, then limited. What does this print? ```java import java.util.stream.*; import static java.util.stream.Gatherers.*; public class Main { public static void main(String[] args) { System.out.println(Stream.iterate(1, n -> n + 1).gather(windowFixed(2)).limit(3).toList()); } } ```

    1. A. The program never terminates

      Gatherers are lazy and the downstream limit short-circuits, so only enough source elements (six) are ever pulled; the program terminates promptly.

    2. B. [[1, 2], [3]]

      Assumes the limit applies to the source before gather (three elements windowed into a pair plus a short [3]); here limit runs on the stream of windows, not the raw elements.

    3. C. [[1, 2, 3]]

      Confuses the limit count 3 with a window size; the window size is fixed at 2, and limit bounds the number of windows.

    4. D. [[1, 2], [3, 4], [5, 6]]Correct answer

      windowFixed(2) lazily groups the infinite stream into pairs, and the limit placed after the gather takes the first three windows and short-circuits, so three windows of size 2 come from six source elements.

    Explanation

    Gatherers are lazy, so a short-circuiting operation downstream is what makes an infinite source safe. Position matters: because the bound is applied after the gather, it counts emitted windows rather than raw source elements, so three windows of the fixed size two are produced from six elements before the pull stops. The infinite source is never fully consumed.

  8. Question 8

    Which two statements about the `java.util.stream.Gatherers` factory class are correct? (Choose two.)

    1. A. Gatherers provides exactly five built-in factory methods: windowFixed, windowSliding, fold, scan, and mapConcurrentCorrect answer

      Gatherers exposes exactly five static factory methods — windowFixed, windowSliding, fold, scan, and mapConcurrent — as confirmed by the class's public API.

    2. B. Gatherers.map(Function) is a convenience factory equivalent to Stream.map

      There is no Gatherers.map factory; mapping is already a first-class Stream operation (Stream.map), so referencing Gatherers.map is a compile error.

    3. C. Gatherers.mapConcurrent preserves the stream's encounter order in its output, regardless of which tasks finish firstCorrect answer

      mapConcurrent evaluates its mapping function on multiple virtual threads concurrently yet always emits results in the source's encounter order, not in task-completion order.

    4. D. Gatherers.distinct() removes duplicate elements during a gather() call

      There is no Gatherers.distinct factory; de-duplication is Stream.distinct, not one of the five built-in gatherers.

    Explanation

    The Gatherers factory class provides exactly five built-in gatherers: two windowing operations, the fold and scan reductions, and mapConcurrent. mapConcurrent runs its mapping function across virtual threads yet preserves encounter order in its output. The deliberate gaps matter: there is no map, filter, distinct, sorted, or zip factory, because those either already exist on Stream or were left out of the initial API, so naming one of them does not compile.

Practise all 18 Stream Gatherers questions

OCP Java SE 25 has the full set, inside timed mock exams that mirror real exam conditions — every question with a worked explanation.

Open OCP Java SE 25

Other topics in this pack