300. Java Stream API - 收集 Stream 元素到集合或数组(Java Stream 收集器详解)
Java 的 Stream API 提供了多种方式来将流中的元素收集到集合中,比如 List、Set、数组等。你在上一节已经见识过其中的一两个例子,本节我们来深入挖掘各种实用模式及其适用场景。
🧭 在选择收集方式前,你应先思考几个关键问题:
- 🔒 是否需要一个 不可变(unmodifiable) 的集合?
- 🧺 是否需要一个具体的集合类型(如
ArrayList、LinkedList或自定义实现)? - 📏 是否能预估元素数量?(这将影响性能优化)
- 🧩 是否需要与第三方库的集合实现兼容?
这些因素会影响你最终的收集方式选择。接下来我们逐一介绍各种场景下的典型收集方式。
🚀 1. 收集到可变 ArrayList(默认方式)
这是最常用也最简单的方式:
Stream<String> strings = Stream.of("one", "two", "three", "four");
List<String> result = strings
.filter(s -> s.length() == 3)
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println("result = " + result);
🧾 输出:
result = [ONE, TWO]
📌 特点:
- 返回一个普通的
ArrayList - 简洁 ✅,可变 ✅,性能中等 ⚠️(当元素数量大时,可能触发内部数组扩容)
🎯 2. 自定义集合类型或预设容量(toCollection)
当你:
- 想使用其他集合类型(如
LinkedList) - 或者你知道将要收集多少元素(可优化容量)
可使用 Collectors.toCollection(...):
Stream<Integer> ints = IntStream.range(0, 10_000).boxed();
List<String> result = ints
.map(String::valueOf)
.collect(Collectors.toCollection(() -> new ArrayList<>(10_000)));
System.out.println("# result size = " + result.size());
🧠 好处:
- 避免
ArrayList多次扩容 - 可替换为
LinkedList::new、CopyOnWriteArrayList::new等工厂方法
🔒 3. 收集为不可变 List(Java 10+)
✅ 使用 Collectors.toUnmodifiableList()
Stream<String> strings = Stream.of("one", "two", "three", "four");
List<String> result = strings
.filter(s -> s.length() == 3)
.map(String::toUpperCase)
.collect(Collectors.toUnmodifiableList());
System.out.println("result = " + result);
📌 输出:
result = [ONE, TWO]
⚠️ 尝试修改 result.add(...) 会抛出 UnsupportedOperationException
🚀 4. 更高效的不可变 List(Java 16+)
✅ 使用新方法 .toList()
Stream<String> strings = Stream.of("one", "two", "three", "four");
List<String> result = strings
.filter(s -> s.length() == 3)
.map(String::toUpperCase)
.toList();
System.out.println("result = " + result);
📌 输出:
result = [ONE, TWO]
⚙️ 为什么更高效?
-
.collect(Collectors.toUnmodifiableList())的底层实现是:- 收集到一个
ArrayList - 再封装为不可变集合(多了一步)
- 收集到一个
-
.toList()则更聪明:如果能预估流的长度,它会一次性分配数组空间,避免扩容与拷贝(JDK 16优化)
🔢 5. 收集到数组(使用 toArray)
❌ 简单版(类型擦除):
Object[] result = Stream.of("a", "b", "c").toArray();
- 不推荐!返回
Object[],类型丢失。
✅ 推荐写法(保留数组类型):
String[] result = Stream.of("one", "two", "three", "four")
.filter(s -> s.length() == 3)
.map(String::toUpperCase)
.toArray(String[]::new);
System.out.println(Arrays.toString(result));
📌 输出:
[ONE, TWO]
🧠 背后的语法糖:
String[]::new == size -> new String[size]
📝 各种收集方式总结对比表
| 模式 | 返回类型 | 可变性 | Java 版本 | 适用场景说明 |
|---|---|---|---|---|
collect(Collectors.toList()) |
ArrayList | ✅ 可变 | Java 8+ | 默认收集方式,简单好用 |
collect(toCollection(...)) |
任意集合类型 | ✅ 可变 | Java 8+ | 自定义集合或设置初始容量 |
collect(toUnmodifiableList()) |
ImmutableList | ❌ 不可变 | Java 10+ | 需要只读集合,防止误修改 |
stream.toList() |
ImmutableList | ❌ 不可变 | Java 16+ | 更高效的不可变收集,推荐 |
toArray(String[]::new) |
数组 | ✅ 可变 | Java 8+ | 需要收集成固定类型数组 |
toArray() |
Object[] | ✅ 可变 | Java 8+ | 不推荐,类型擦除 |