Definition
- toArray() – returns
Object[]; the element type is erased due to generics. - toArray(IntFunction) – the generator receives the array size and creates a typed array:
String[]::newissize -> new String[size]. - Why needed – Java cannot create
new T[]at runtime due to type erasure, so you must provide the array constructor. - Primitive streams –
IntStream.toArray()returnsint[]directly because primitive types are not erased.
toArray() without arguments is like the bank giving you a generic bag of items. toArray(String[]::new) is like the bank giving you a properly labeled filing cabinet with the right-sized drawers. You tell the bank the drawer type (generator), and it fills them correctly.
Understanding the Concepts
How do the toArray overloads work?
import java.util.stream.Stream;
// No-arg: returns Object[]
Object[] objects = Stream.of("SAV2001", "CUR3001", "FD4001").toArray();
// objects is Object[], not String[]!
// (String[]) objects → ClassCastException!
// With generator: returns String[]
String[] accounts = Stream.of("SAV2001", "CUR3001", "FD4001")
.toArray(String[]::new);
// accounts is String[]
Why does casting fail?
// WRONG: runtime array component type is Object
Object[] obj = Stream.of("SAV2001", "CUR3001").toArray();
String[] strings = (String[]) obj; // ClassCastException!
// The array was created as new Object[2], not new String[2].
// CORRECT: generator creates String[]
String[] strings = Stream.of("SAV2001", "CUR3001").toArray(String[]::new);
Java cannot create new T[] at runtime due to type erasure. The generator provides the array constructor.
How does the generator work internally?
// String[]::new is shorthand for:
// (int size) -> new String[size]
// The stream:
// 1. Counts elements
// 2. Calls generator(count) to create the array
// 3. Fills the array with elements
// 4. Returns the typed array
String[] ids = trades.stream()
.map(Trade::getId)
.toArray(String[]::new); // generator receives the size, creates String[size]
How does it work for primitive streams?
import java.util.stream.IntStream;
int[] numbers = IntStream.rangeClosed(1, 5).toArray();
// [1, 2, 3, 4, 5] – no generator needed for primitives!
long[] longs = java.util.stream.LongStream.of(100L, 200L, 300L).toArray();
double[] doubles = java.util.stream.DoubleStream.of(1.5, 2.5, 3.5).toArray();
Primitive types are not subject to type erasure, so the runtime knows the array component type.
How do you use toArray with custom types?
package com.bank.service;
import java.math.BigDecimal;
import java.util.List;
public class ToArrayDemo {
record Account(String id, BigDecimal balance) {}
public static void main(String[] args) {
List<Account> accounts = List.of(
new Account("SAV2001", new BigDecimal("125000")),
new Account("CUR3001", new BigDecimal("250000")));
// Custom type array
Account[] arr = accounts.stream().toArray(Account[]::new);
System.out.println(arr.length); // 2
System.out.println(arr[0].id()); // SAV2001
// BigDecimal array from mapped stream
BigDecimal[] balances = accounts.stream()
.map(Account::balance)
.toArray(BigDecimal[]::new);
}
}
What is the comparison of collection methods?
| Method | Returns | Mutable | Type-safe |
|--------|---------|:---:|:---:|
| toList() (Java 16) | Unmodifiable List
| collect(toList()) | Mutable List
| toArray() | Object[] | Yes | No |
| toArray(T[]::new) | T[] | Yes | Yes |
What are common mistakes?
// WRONG: casting no-arg toArray result
String[] bad = (String[]) Stream.of("A", "B").toArray(); // ClassCastException!
// CORRECT: use generator
String[] good = Stream.of("A", "B").toArray(String[]::new);
// WRONG: using old-style pre-sized array
// Stream has no toArray(T[]) like Collection does
// Stream.toArray only accepts IntFunction<T[]>
// CORRECT: pass constructor reference
String[] result = stream.toArray(String[]::new);
What should a senior engineer know?
- The generator
String[]::newis equivalent to(int size) -> new String[size]. - For custom types:
Trade[]::newcreates a Trade array. - toArray is a terminal operation that consumes the stream.
- Prefer toList() (Java 16) or collect(toList()) over toArray for most use cases. Use toArray when an API requires an array.
- IntStream.toArray() returns int[] without a generator because primitive types are not erased.
Visual Overview
toArray() vs toArray(generator):
toArray():
Stream<String> → Object[] (type info lost)
Cannot cast to String[] → ClassCastException
toArray(String[]::new):
Stream<String> → generator(3) → new String[3]
→ fill ["SAV", "CUR", "FD"]
→ String[] (correctly typed)
IntStream.toArray():
IntStream → int[] (no erasure for primitives)
Key Takeaway
toArray() without arguments returns Object[] because generics type erasure prevents creating T[] at runtime. The array's runtime component type is Object, so casting it to String[] throws ClassCastException. Always use toArray(String[]::new) to get a correctly-typed array.
The generator String[]::new is an IntFunction that receives the required array size and creates a new String array of that size. The stream fills it with elements. For primitive streams, toArray() returns int[], long[], or double[] directly because primitive types are not subject to erasure.
Remember one sentence: always use toArray(Type[]::new) for typed arrays because the no-arg toArray() returns Object[] due to type erasure.
---