Stream API practice questions

From OCP Java SE 17 (1Z0-829) · 19 questions on this topic

Stream API practice questions from OCP Java SE 17 (1Z0-829). This pack has 19 questions tagged Stream API, 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 API

  1. Question 1

    Stream.iterate is called with three arguments. What does this program print? ```java import java.util.stream.*; public class Main { public static void main(String[] args) { String s = Stream.iterate(1, i -> i < 20, i -> i * 3) .map(String::valueOf) .collect(Collectors.joining("-")); System.out.println(s); } } ```

    1. A. 3-9

      Wrong: this assumes the seed is not part of the stream, as if next were applied once before the first emission. The seed is always the first candidate element, so 1 is emitted.

    2. B. 1-3-9Correct answer

      Correct: the three-argument iterate tests the predicate before each emission including the seed, so 1, 3, 9 pass i < 20 and 27 fails; joined with dashes they give 1-3-9.

    3. C. The program never terminates because Stream.iterate produces an infinite stream

      Wrong: this describes the two-argument iterate(seed, next), which is infinite and needs a short-circuiting op. The three-argument overload is bounded by its predicate and is finite.

    4. D. 1-3-9-27

      Wrong: this treats the predicate as a do-while condition - emit first, test afterwards - so 27 slips out before the check fails. The predicate is a hasNext, checked before emission.

    Explanation

    Trace: the three-argument `Stream.iterate(seed, hasNext, next)` is the stream analogue of a `for` loop — the predicate is tested *before* each element is emitted, including the seed. So it tests 1 (< 20, emit), applies next to get 3 (< 20, emit), then 9 (< 20, emit), then 27, which fails `i < 20`, so the stream ends without emitting it. Three elements survive, `map` stringifies them and `joining("-")` glues them with a single dash between neighbours: `1-3-9`. Why the others are wrong: `1-3-9-27` treats the predicate as a do-while condition — emit first, test afterwards — so 27 slips out before the check fails. The predicate is a `hasNext`, checked before emission. `3-9` assumes the seed is not part of the stream, as if `next` were applied once before the first emission. The seed is always the first candidate element. `The program never terminates because Stream.iterate produces an infinite stream` describes the *two*-argument `iterate(seed, next)`, which is indeed infinite and needs a short-circuiting op such as `limit`. The three-argument overload (Java 9) is bounded by its predicate and is a finite stream. Exam tip: `Stream.iterate(1, i -> i < 20, i -> i * 3)` maps one-to-one onto `for (int i = 1; i < 20; i *= 3)` — seed, condition, update, condition checked first. Reverse trap: with only two arguments the stream is infinite, and a terminal op like `collect` or `count` on it hangs forever.

  2. Question 2

    A list of lists is flattened and each element is doubled. What does this program print? ```java import java.util.*; public class Main { public static void main(String[] args) { List<List<Integer>> nested = List.of(List.of(1, 2), List.of(), List.of(3, 4, 5)); List<Integer> flat = nested.stream() .flatMap(List::stream) .map(n -> n * 2) .toList(); System.out.println(flat); } } ```

    1. A. The code does not compile because List::stream cannot be passed to flatMap

      Assumes flatMap needs a factory like Stream::of; its parameter is a Function returning a Stream, and List::stream is exactly such a function, so it compiles.

    2. B. [[2, 4], [], [6, 8, 10]]

      This is what map(list -> ...) gives, assuming flatMap preserves nesting; flatMap flattens a stream of streams into a stream of elements.

    3. C. [2, 4]

      Assumes the empty List.of() terminates flattening; an empty inner stream simply contributes zero elements and traversal continues.

    4. D. [2, 4, 6, 8, 10]Correct answer

      flatMap splices the contents of each inner stream into one flat stream (1..5, the empty list adding nothing), then map doubles each, giving [2, 4, 6, 8, 10].

    Explanation

    Trace: `flatMap` maps each element to a stream and then splices the *contents* of that stream into the pipeline, so the three inner streams (1, 2), (nothing) and (3, 4, 5) are concatenated into a single flat stream of 1, 2, 3, 4, 5. The empty inner list simply contributes zero elements — it is not a terminator. `map(n -> n * 2)` then doubles each of the five values and `toList()` collects them in encounter order, so `List.toString` prints `[2, 4, 6, 8, 10]`. Why the others are wrong: `[[2, 4], [], [6, 8, 10]]` is what `map(list -> ...)` would give — it assumes `flatMap` preserves the nesting and merely transforms each inner list. Flattening is the whole point of `flatMap`; a stream of streams comes back as a stream of elements. `[2, 4]` assumes the empty `List.of()` ends the flattening, as if an empty inner stream short-circuits the outer one. An inner stream that yields no elements is skipped and traversal continues with the next element. `The code does not compile because List::stream cannot be passed to flatMap` encodes the belief that `flatMap` needs a factory such as `Stream::of`. Its parameter is `Function<? super T, ? extends Stream<? extends R>>`, and `List::stream` is exactly such a function. Exam tip: `map` is 1-to-1, `flatMap` is 1-to-many (including 1-to-zero). Whenever a lambda would return a `Stream`, `Collection` or `Optional` and you want its contents rather than the container itself, `flatMap` is the operation. Reverse trap: using `flatMap` where the mapper returns a plain value does not compile, because a value is not a `Stream`.

  3. Question 3

    What is the output? ```java import java.util.*; import java.util.stream.*; public class Main { public static void main(String[] args) { Optional<Integer> r = Stream.of(1,2,3,4) .filter(n -> n % 2 == 0) .reduce((a,b) -> a + b); System.out.println(r.get()); } } ```

    1. A. 10

      This is the sum of all four elements; the filter removed the odd values before the reduction, so they must not be included.

    2. B. 2

      Assumes the reduction returns only the first surviving element, but reduce accumulates every element rather than picking one.

    3. C. 4

      This is merely the last surviving element; reduce combines the elements, it does not select a single one.

    4. D. 6Correct answer

      Correct: the survivors 2 and 4 are combined left to right by the accumulator to give 6, wrapped in an Optional that get() then unwraps.

    Explanation

    The filter keeps only the even values, and the single-argument reduce with no identity folds those survivors together left to right, producing their combined sum wrapped in an Optional because the stream could have been empty. Unwrapping the Optional prints that total. The one-argument reduce returns Optional precisely because it has no seed value to fall back on.

  4. Question 4

    Which two statements about the collectors in `java.util.stream.Collectors` are correct? (Choose two.)

    1. A. In groupingBy(String::length, Collectors.counting()) the value type of the resulting map is LongCorrect answer

      Collectors.counting() is declared Collector<T,?,Long>, so using it as the downstream of groupingBy produces a Map whose value type is Long.

    2. B. Collectors.partitioningBy returns a map containing both the false and true keys even when one partition matches no elementsCorrect answer

      partitioningBy always splits into exactly two buckets, so both the false and true keys are present even when one partition is empty (its value being an empty list, not a missing key).

    3. C. Collectors.groupingBy(classifier) returns a map whose keys are iterated in ascending natural order

      Confuses grouping with sorting; groupingBy returns an unordered HashMap with no ordering guarantee, so keys are not iterated in ascending natural order (a TreeMap factory is needed for that).

    4. D. The List returned by Collectors.toList() is unmodifiable, so calling add on it throws UnsupportedOperationException

      Swaps the two toLists; Collectors.toList() returns a mutable ArrayList that accepts add, whereas it is Stream.toList() that returns an unmodifiable list throwing UnsupportedOperationException.

    Explanation

    `In groupingBy(String::length, Collectors.counting()) the value type of the resulting map is Long` is correct: `Collectors.counting()` is declared as `Collector<T, ?, Long>`, so a `groupingBy` that uses it as its downstream produces a `Map<K, Long>`. Grouping "a", "bb", "cc" by length and reading the bucket for key 2 yields a value whose runtime class is `java.lang.Long` (value 2) — a common trip-up when the map is declared as `Map<Integer, Integer>`, which does not compile. `Collectors.partitioningBy returns a map containing both the false and true keys ...` is correct: partitioning is defined to split into exactly two buckets, and both are always present. Partitioning 1, 3, 5 on `n % 2 == 0` gives `{false=[1, 3, 5], true=[]}` — a map of size 2 whose `true` entry is an empty list, not a missing key. That is precisely why `partitioningBy` is safe to `get(true)` on without a null check, while `groupingBy` is not. Why the others are wrong: `Collectors.groupingBy(classifier) returns a map whose keys are iterated in ascending natural order` confuses grouping with sorting. The Javadoc states there are no guarantees on the type, mutability, serializability or thread-safety of the returned map; in practice it is a `HashMap`. Grouping "zebra", "apple", "mango" by first character iterates its keys as `[a, z, m]` — not ascending. Pass a map factory such as `TreeMap::new` (the three-argument `groupingBy`) if you need sorted keys. `The List returned by Collectors.toList() is unmodifiable ...` swaps the two "toList"s. `Collectors.toList()` gives back a mutable `java.util.ArrayList`, and `add` on it succeeds. It is `Stream.toList()` (added in Java 16) that returns an unmodifiable list and throws `UnsupportedOperationException` on `add`. Note the Javadoc guarantees no more than that for `Collectors.toList()` — do not rely on mutability either; use `Collectors.toCollection(ArrayList::new)` when you need it. Exam tip: know the shape of each collector's result — `counting()` is `Long`, `summingInt` is `Integer`, `averagingInt` is `Double`, `partitioningBy` always has both boolean keys, and `groupingBy` gives an unordered map. Reverse trap: `Stream.toList()` and `Collectors.toList()` look interchangeable but differ in mutability.

  5. Question 5

    Words are collected into a map of word to length. What does this program print? ```java import java.util.*; import java.util.stream.*; public class Main { public static void main(String[] args) { Map<String, Integer> lengths = Stream.of("kiwi", "fig", "banana") .collect(Collectors.toMap(s -> s, String::length, (a, b) -> a, TreeMap::new)); System.out.println(lengths); } } ```

    1. A. {banana=6, fig=3, kiwi=4}Correct answer

      Correct: the TreeMap::new supplier makes the four-argument toMap build a TreeMap, so entries iterate in ascending natural (alphabetical) key order regardless of the stream's encounter order.

    2. B. {fig=3, kiwi=4, banana=6}

      Wrong: this sorts by the value (3, 4, 6) rather than by the key. A TreeMap orders by key; sorting on values would need an explicit comparator over the entries.

    3. C. {kiwi=4, fig=3, banana=6}

      Wrong: this assumes the map preserves stream encounter order, ignoring the TreeMap::new supplier. Only a LinkedHashMap supplier would give encounter order.

    4. D. The code does not compile because toMap does not accept a map supplier

      Wrong: this encodes the belief that toMap has only two- and three-argument forms. There is a four-argument overload whose last parameter is a Supplier for the result map, which is exactly what TreeMap::new supplies here.

    Explanation

    Trace: the four-argument `toMap` takes a key mapper, a value mapper, a merge function and a map supplier. Each word becomes a key, its length becomes the value, and the supplier `TreeMap::new` decides the container — so the entries live in a `TreeMap` and iterate in ascending natural (alphabetical) key order regardless of the stream's encounter order. `TreeMap.toString` therefore prints `{banana=6, fig=3, kiwi=4}`. The merge function `(a, b) -> a` is never called here because the three words are distinct keys. Why the others are wrong: `{kiwi=4, fig=3, banana=6}` assumes the map preserves stream encounter order, ignoring the `TreeMap::new` supplier. Only a `LinkedHashMap` supplier would give that. `{fig=3, kiwi=4, banana=6}` sorts by the *value* (3, 4, 6) rather than the key. A `TreeMap` orders by key; sorting on values needs an explicit comparator over the entries. `The code does not compile because toMap does not accept a map supplier` encodes the belief that `toMap` only has two- and three-argument forms. There is a four-argument overload whose last parameter is a `Supplier<M>` for the result map — that is the only way to choose the map type. Exam tip: a `toMap` or `groupingBy` result is a `HashMap` with no order guarantee unless you supply a map factory. `TreeMap::new` gives sorted keys, `LinkedHashMap::new` gives encounter order. Reverse trap: the merge function is not optional decoration — drop it from a stream with duplicate keys and `toMap` throws `IllegalStateException`.

  6. Question 6

    What is printed (map toString order aside, focus on the values)? ```java import java.util.*; import java.util.stream.*; public class Main { public static void main(String[] args) { Map<Integer,Long> m = Stream.of("a","bb","cc","ddd") .collect(Collectors.groupingBy(String::length, Collectors.counting())); System.out.println(m.get(2)); } } ```

    1. A. 2Correct answer

      Correct: grouping by length with a counting downstream gives {1=1, 2=2, 3=1}, and the length-two group contains "bb" and "cc", so its count is 2.

    2. B. 1

      This is the count for the length-one or length-three groups; two strings share length two, so its count is not one.

    3. C. 3

      No length occurs three times in this data, so a count of three matches nothing here.

    4. D. [bb, cc]

      This would be the value only if the default toList() downstream were used; counting() replaces the list with a Long count.

    Explanation

    A downstream counting collector makes groupingBy map each distinct key to the number of elements in that group rather than to a list, so the value type becomes Long. Two of the strings share length two, so the count stored under that key is two. The downstream collector replaces the default toList(), which is why the value is a count and not a list of strings.

  7. Question 7

    What is the output? ```java import java.util.stream.*; public class Main { public static void main(String[] args) { int sum = IntStream.rangeClosed(1, 5).sum(); System.out.println(sum); } } ```

    1. A. 10

      This is what range(1, 5) would give: the half-open form excludes the upper bound, summing only 1+2+3+4.

    2. B. 14

      Matches no variant of the range; it would require dropping the 1 from the closed range.

    3. C. 20

      Corresponds to no range here, such as 2..6; both bounds are exactly as written.

    4. D. 15Correct answer

      Correct: rangeClosed(1, 5) is inclusive of 5, so 1+2+3+4+5 = 15.

    Explanation

    rangeClosed includes its upper bound, so it produces one through five, and summing them gives fifteen. The half-open range form would exclude the upper bound and produce a smaller total. The exam hides this off-by-one inside a sum so that a wrong bound lands on one of the distractors.

  8. Question 8

    The three-argument groupingBy below totals order quantities per category. What does this program print? ```java import java.util.*; import java.util.stream.*; public class Main { record Item(String cat, int qty) {} public static void main(String[] args) { List<Item> items = List.of( new Item("fruit", 3), new Item("veg", 5), new Item("fruit", 4), new Item("dairy", 2), new Item("veg", 1)); Map<String, Integer> totals = items.stream() .collect(Collectors.groupingBy(Item::cat, TreeMap::new, Collectors.summingInt(Item::qty))); System.out.println(totals); } } ```

    1. A. {fruit=7, veg=6, dairy=2}

      Has the right totals but encounter-order keys, which is what an insertion-ordered map factory would give; the supplied sorted-map factory orders keys alphabetically.

    2. B. {dairy=1, fruit=2, veg=2}

      Counts the items per group instead of summing their quantities, which is counting rather than summing behaviour.

    3. C. {dairy=2, fruit=4, veg=1}

      Keeps only the last item's quantity per key (a map-merge behaviour); the summing collector adds the quantities of all items in each group.

    4. D. {dairy=2, fruit=7, veg=6}Correct answer

      Correct — the sorted-map factory orders keys alphabetically (dairy, fruit, veg) and the summing collector totals each group's quantities (fruit 3+4=7, veg 5+1=6, dairy 2).

    Explanation

    The three-argument grouping collector builds its result with the supplied map factory, and a sorted-map factory arranges the keys in natural (alphabetical) order rather than encounter order. Its downstream summing collector adds up each group's integer quantities, as opposed to merely counting the elements or keeping only the last one per key.

Practise all 19 Stream API questions

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

Open OCP Java SE 17

Other topics in this pack