Definition
- collect(toList()): returns a flexible
Listthat supports generics and integrates with Collections. - toArray(T[]::new): returns a fixed-size
T[]needed by specific APIs. - Primitive streams:
IntStream.toArray()returnsint[]without boxing. - Java 16 toList(): returns an unmodifiable list (different from
Collectors.toList()). - Rule: start with List, convert to array only when required.
At National Bank of India, Priya keeps results in a flexible folder (List) by default, and only puts them in a fixed box (array) when some legacy system specifically needs that box shape.
Understanding the Concepts
What are the key differences?
| Aspect | collect(toList()) | toArray(T[]::new) |
|---|---|---|
| Result type | List (mutable ArrayList) | T[] (fixed-size array) |
| Generics | Full support | Limited (arrays and generics mix poorly) |
| Modification | Resizable, add/remove | Fixed size, only replace elements |
| Composability | Re-stream, Collections API | Manual iteration |
| Use when | Most application code | API requires array, varargs, primitives |
When should you use toArray?
package com.bank.hr;
import java.math.BigDecimal;
import java.util.List;
public class ToArrayVsToList {
record Employee(String id, String name, BigDecimal salary) {}
public static void main(String[] args) {
List<Employee> staff = List.of(
new Employee("SAV2001", "Rajesh", new BigDecimal("1800000")),
new Employee("SAV2002", "Priya", new BigDecimal("1200000")),
new Employee("SAV2003", "Meera", new BigDecimal("1500000"))
);
// Use case 1: API requires an array
Employee[] array = staff.stream().toArray(Employee[]::new);
legacyReport(array);
// Use case 2: Primitive array without boxing
int[] salaryInts = staff.stream()
.mapToInt(e -> e.salary().intValue())
.toArray(); // int[], no boxing
// Default: use toList for everything else
List<Employee> list = staff.stream()
.filter(e -> e.salary().compareTo(new BigDecimal("1300000")) > 0)
.toList();
}
static void legacyReport(Employee[] employees) {
System.out.println("Legacy report: " + employees.length + " employees");
}
}
What about Java 16's Stream.toList()?
// Collectors.toList(): returns mutable ArrayList
List<String> mutable = staff.stream().map(Employee::name)
.collect(java.util.stream.Collectors.toList());
mutable.add("NewName"); // works
// Stream.toList() (Java 16+): returns unmodifiable list
List<String> unmodifiable = staff.stream().map(Employee::name).toList();
// unmodifiable.add("NewName"); // throws UnsupportedOperationException
What are common mistakes?
Mistake: Using toArray when a List would be simpler.
// WRONG: unnecessary array conversion
Employee[] arr = staff.stream().filter(predicate).toArray(Employee[]::new);
for (Employee e : arr) { process(e); }
// CORRECT: just use a List
List<Employee> list = staff.stream().filter(predicate).toList();
list.forEach(this::process);
What should a senior engineer know?
Default to List for flexibility. Reserve toArray for: (1) primitive performance via primitive-stream toArray, (2) API/varargs interoperability, and (3) fixed-size contracts. Converting a list to an array later is trivial if needed. Know the difference between Collectors.toList() (mutable) and Stream.toList() (unmodifiable).
What are common interview follow-up questions?
- "What is the difference between Collectors.toList() and Stream.toList()?" Mutability: Collectors gives ArrayList, Stream.toList() gives unmodifiable.
- "When does toArray outperform toList?" Rarely: the difference is negligible for reference types. Primitive streams benefit from toArray returning int[]/long[] without boxing.
- "Can you convert a list to an array?" Yes:
list.toArray(Employee[]::new).
Visual Overview
Decision tree: ┌─────────────────────────────────────────┐ │ Do you need the elements? │ │ ├── No → use count() │ │ └── Yes │ │ ├── API requires array? │ │ │ └── Yes → toArray(T[]::new) │ │ ├── Primitive without boxing? │ │ │ └── Yes → IntStream.toArray() │ │ └── General use? │ │ └── Yes → toList() │ └─────────────────────────────────────────┘
Key Takeaway
Default to collect(toList()) for most application code because a List is flexible, generic-friendly, and composable. Use toArray(T[]::new) only when an API specifically requires an array, for varargs, or for primitive streams where IntStream.toArray() returns an efficient int[] without boxing.
At the National Bank of India, most reporting returns lists, and Rajesh only produces an array for one legacy signature that requires it. Converting a list to an array later is trivial with list.toArray(T[]::new), so starting with a list is the practical default.
Java 16's Stream.toList() returns an unmodifiable list, while Collectors.toList() typically gives a mutable ArrayList. Choose based on whether the caller needs to modify the result.
Remember one sentence: default to toList() for flexibility and use toArray() only for array-demanding APIs, varargs, or primitive streams.
---