skip to content

How do min/max, count, and toArray behave as terminal operations — including the Comparator requirement, the Optional return, and the generator form of toArray?

level: middleimportance: should knowfreq 45%

answer

  1. min/max need a Comparator, return Optional (empty on empty stream)
  2. count returns long; may skip traversal when size is known (Java 9+)
  3. toArray() -> Object[]; toArray(String[]::new) -> typed array
  4. Primitive streams: OptionalInt, int[], no comparator, no boxing
  5. Don't rely on count() to trigger side effects

basics

~20 s

min and max need a Comparator and return an Optional (empty if the stream is empty). count returns the number of elements as a long. toArray() returns an Object[]; use toArray(String[]::new) to get a typed array of the right element type.

solid answer

~40 s

min(Comparator) and max(Comparator) are reduction terminal ops that find the smallest/largest element. They require a Comparator because a generic stream element may not be Comparable, and they return Optional<T> — empty when the stream has no elements — so you must unwrap with orElse/orElseThrow. count() returns a long and, since Java 9, can be computed without traversal when the size is known and no side-effecting intermediate ops are present (an optimization). toArray() with no args returns Object[]; to get a correctly-typed array you pass a generator: toArray(String[]::new), which the runtime uses to allocate an array of the right component type. For primitive streams (IntStream etc.) min/max return OptionalInt and toArray returns int[], avoiding boxing.

code

java · 17 lines
java
List<String> words = List.of("fig", "apple", "kiwi");

// min/max need a Comparator and return Optional
String shortest = words.stream()
    .min(Comparator.comparingInt(String::length))
    .orElseThrow();                         // "fig"

// count -> long
long n = words.stream().count();            // 3

// typed array via generator
String[] arr = words.stream().toArray(String[]::new);
Object[] objs = words.stream().toArray();   // Object[]

// primitive stream: no comparator, OptionalInt, int[]
OptionalInt maxLen = words.stream().mapToInt(String::length).max();  // 5
int[] lens = words.stream().mapToInt(String::length).toArray();

go deeper

for a junior

Knows min/max take a Comparator and return Optional, count returns a number, and toArray builds an array.

for a middle

Explains the Object[] vs typed toArray(generator) difference, why Optional is returned, and primitive-stream variants.

for a senior

Discusses the count() no-traversal optimization and its side-effect implications, plus boxing avoidance with primitive streams.

for a principal

Guides API choices (Optional unwrapping conventions, primitive streams for hot paths) and reviews for the count side-effect trap.

## These are all terminal reductions/materializers `min`, `max`, `count`, and `toArray` are **terminal operations**: they consume the stream and produce a concrete result. The first three reduce the stream to a single value; `toArray` materializes the elements into an array. ## min / max — need a Comparator, return Optional A **`Comparator<T>`** is an object that defines an ordering by comparing two elements. `min(Comparator)` returns the element that the comparator ranks **smallest**; `max(Comparator)` returns the **largest**. Why is a comparator **required**? Because a `Stream<T>` element type `T` is arbitrary — it might not implement `Comparable`, and even if it does, you may want a different ordering (by name, by length, reversed). The library refuses to guess an ordering, so you supply one (e.g. `Comparator.comparingInt(String::length)`). They return **`Optional<T>`** (an object that may or may not hold a value) because an **empty stream has no min or max**. Instead of returning null, you get `Optional.empty()`, forcing you to handle the empty case explicitly: ```java Optional<String> longest = words.stream() .max(Comparator.comparingInt(String::length)); String result = longest.orElse("<none>"); ``` `min`/`max` are essentially `reduce` with a comparator-based binary operator. ## count — returns a long `count()` returns the number of elements as a **`long`** (not int — streams can be huge). **Optimization (Java 9+):** if the stream's size is **known** from the source (e.g. a sized collection) and the pipeline has **no side-effecting or size-changing intermediate operations that must run**, the JDK may return the count **without actually traversing** the elements. A subtle consequence: a side effect you tucked into a `peek()` or `map()` before `count()` might **not execute**, because the library short-circuits to the known size. So never rely on `count()` to drive side effects. ## toArray — Object[] vs typed array - **`toArray()`** (no args) returns **`Object[]`** — because of type erasure the runtime cannot know `T` to create a `T[]`. - **`toArray(IntFunction<A[]> generator)`** returns a **typed `A[]`**. You pass an array constructor reference: `toArray(String[]::new)`. The generator receives the size and allocates an array of the correct **component type**, so you get a real `String[]` you can use without casting. ```java String[] arr = stream.toArray(String[]::new); // typed Object[] objs = stream.toArray(); // Object[] ``` ## Primitive streams avoid boxing For **primitive streams** (`IntStream`, `LongStream`, `DoubleStream`): - `min()`/`max()` take **no comparator** (natural numeric order) and return `OptionalInt`/`OptionalLong`/`OptionalDouble`. - `toArray()` returns `int[]`/`long[]`/`double[]` directly — no boxing into `Integer[]`. - `count()` still returns `long`. Using primitive streams here is more efficient because it avoids wrapping each value in a boxed object. ## Putting it together - Need the extreme element by some ordering → `min`/`max` with a `Comparator`, unwrap the `Optional`. - Need how many → `count()` (long; don't hang side effects on it). - Need an array → `toArray(T[]::new)` for a typed array; primitive streams give native arrays.

  • Why does max return an Optional instead of the element directly?
    Because an empty stream has no maximum. Returning Optional.empty() instead of null forces callers to handle the no-element case explicitly and avoids NullPointerExceptions.
  • How do you get a String[] instead of Object[] from a stream?
    Pass an array generator: stream.toArray(String[]::new). The generator allocates an array of the correct component type, so the result is a real String[] without casting.

min/max are like asking a judge (the Comparator) to pick the smallest/largest contestant — and the judge hands back a sealed envelope (Optional) that might be empty if no one showed up. toArray(String[]::new) is giving the packer the right-sized labeled box so items come out sorted into a String[] rather than a generic crate (Object[]).

saying these in an interview costs you the question

  • Calling stream.min()/max() without a Comparator on a non-primitive stream (won't compile)
  • Assuming toArray() returns a typed array — it returns Object[]
  • Treating min/max as returning the element directly instead of an Optional
  • Relying on side effects in peek/map before count(), which may be skipped
  • Using count as int (it is long)

context