When would you use the three-argument collect(supplier, accumulator, combiner) instead of collect(Collector), and how do the two forms relate?
answer
- Same mutable reduction, different packaging
- three-arg = identity finisher + empty characteristics
- three-arg can't be CONCURRENT or UNORDERED
- Collector adds finisher + characteristics + reuse
- library collector first; three-arg for one-off containers
basics
~20 sThe three-arg collect lets you spell out the container, how to add an element, and how to merge two containers, all inline. collect(Collector) is the packaged version using ready-made collectors like Collectors.toList(). Prefer the packaged ones; use three-arg for quick one-off containers.
solid answer
~50 sBoth forms perform the same mutable reduction; they differ in packaging. collect(Collector) takes a reusable Collector object that bundles a supplier, accumulator, combiner, and finisher, plus characteristics flags. The three-arg collect(supplier, accumulator, combiner) is a convenience that lets you supply those first three functions inline without authoring a Collector — internally it is equivalent to a Collector with an identity finisher and no special characteristics. Reach for the three-arg form for a quick, ad-hoc accumulation into an arbitrary mutable container that no standard Collector targets, e.g. collecting into a custom container or a third-party builder. Prefer collect(Collector) when a library collector already exists (toList, toMap, groupingBy, joining) because it is more readable, composable, and may carry useful characteristics like UNORDERED or CONCURRENT that the three-arg form cannot express. The three-arg form always behaves as a plain IDENTITY_FINISH collector with no characteristics, so it can never run as a concurrent collector.
go deeper
Knows both forms gather elements into a container and that Collectors.toList() is the usual choice; may not know the three-arg form.
Can write the three-arg form, explain its supplier/accumulator/combiner, and knows it equals an IDENTITY_FINISH collector with no characteristics; picks library collectors by default.
Explains the missing finisher and characteristics, the concurrency consequence, and the combiner-correctness obligation when hand-writing the combiner.
Weighs readability/composability/characteristics of library collectors against the throwaway simplicity of the three-arg form, and decides when authoring a custom Collector is warranted.
## Two doors to the same room `Stream` offers two `collect` overloads, and both do **mutable reduction** (accumulating elements into one mutable container): 1. `collect(Collector<T,A,R> collector)` — pass a packaged **Collector**. 2. `collect(Supplier<R> supplier, BiConsumer<R,T> accumulator, BiConsumer<R,R> combiner)` — pass three loose functions. ### The three-arg form You give three functions directly: ```java List<String> names = people.stream() .map(Person::name) .collect(ArrayList::new, ArrayList::add, ArrayList::addAll); ``` - **supplier** `ArrayList::new` — makes an empty container. - **accumulator** `ArrayList::add` — adds one element (by side effect). - **combiner** `ArrayList::addAll` — merges a second partial container in (parallel only). This is handy when you want to accumulate into **any** mutable thing — a custom class, a `StringBuilder`, a third-party builder — without writing a full `Collector`. ### The Collector form A **Collector** is an object (interface `Collector<T,A,R>`) that bundles: - `supplier()`, `accumulator()`, `combiner()` — the same three functions, plus - `finisher()` — a `Function<A,R>` that converts the intermediate accumulation type `A` to the final result `R`, and - `characteristics()` — a set of flags: `CONCURRENT`, `UNORDERED`, `IDENTITY_FINISH`. Standard library collectors live in `java.util.stream.Collectors`: `toList()`, `toSet()`, `toMap()`, `groupingBy()`, `partitioningBy()`, `joining()`, `counting()`, `summingInt()`, etc. ```java List<String> names = people.stream().map(Person::name).collect(Collectors.toList()); ``` ### How they relate The three-arg form is just **sugar**: it is equivalent to building a Collector whose finisher is the identity function (`A` *is* `R`, no transform) and whose characteristics set is **empty**. Two consequences follow from "empty characteristics": - It is **never CONCURRENT** — even in a parallel stream it uses the split-and-merge (combiner) strategy, one container per chunk, rather than a single shared concurrent container. - It is **never UNORDERED** — it preserves encounter order where the stream is ordered. - Because there is **no finisher**, the result type `R` you collect into is exactly the container the supplier creates; you can't post-transform it (e.g. wrap it unmodifiable) the way a Collector's finisher can. ### When to choose which - **Prefer `collect(Collector)`** when a library collector fits: it is clearer, composable (e.g. `groupingBy(..., toList())`), and can carry characteristics that unlock concurrent collection or order relaxation. It is also reusable across pipelines. - **Use the three-arg form** for a quick, local accumulation into a container no standard collector produces, when you don't need a finisher or characteristics, and authoring a full `Collector` would be overkill. ### Pitfall: the accumulator/combiner must agree In either form, the combiner must produce the same logical result as sequential accumulation; otherwise parallel runs are wrong. With the three-arg form you write the combiner yourself, so this is your responsibility — e.g. forgetting `addAll` semantics or supplying a combiner that drops elements.
- Can the three-arg collect transform its container into an unmodifiable view at the end?No. It has no finisher, so the result is exactly what the supplier creates. To post-transform you need a Collector with a finisher, e.g. Collectors.collectingAndThen(toList(), Collections::unmodifiableList).
- Why might a Collector outperform the three-arg form on a parallel stream?A Collector can declare the CONCURRENT characteristic, letting all threads accumulate into one shared concurrent container, avoiding the cost of building and merging many partial containers. The three-arg form always splits and merges.
saying these in an interview costs you the question
- Thinking the three-arg form can run as a concurrent collector (its characteristics set is empty)
- Believing the two forms produce different results — they do the same reduction
- Assuming the three-arg form has a finisher to post-transform the container