1. 概述
本文将演示如何将 List<E>
转换为 Map<K, List<E>>
类型的结构。✅
核心实现依赖 Java 8 的 Stream API 和 函数式接口 Supplier,通过自定义 Supplier
来控制中间集合的创建方式,比如使用 LinkedHashMap
或 CopyOnWriteArrayList
等特定实现。
这种技巧在实际开发中非常实用,尤其是在需要定制集合类型(如线程安全、有序性)时,避免默认的 HashMap
或 ArrayList
带来的潜在问题。踩坑警告 ⚠️:别小看这个 Supplier,用好了能解决很多隐蔽的并发或顺序问题。
2. JDK 8 中的 Supplier 接口
Supplier<T>
是一个函数式接口,定义如下:
@FunctionalInterface
public interface Supplier<T> {
T get();
}
它常被用作“对象工厂”——你不需要立刻创建实例,而是传入一个能“生产”该实例的函数引用(例如 ArrayList::new
)。在集合操作中,这让我们可以延迟初始化或按需创建容器。
常见用途包括:
- ✅ 作为
collect
方法的参数,指定使用哪种Map
实现(如TreeMap
保证排序) - ✅ 控制 value 集合的类型,比如使用
LinkedList
而非ArrayList
- ✅ 结合 Guava 的
Memoizer
实现缓存(本文不展开)
简单粗暴地说:你想用啥集合,就丢个 ::new
进去,Stream 帮你造。
3. 将 List 转换为 Map
Java Stream 提供了多种聚合方式,但并非所有方法都支持自定义 Supplier
。我们重点关注以下三种支持自定义 Map
和 List
创建逻辑的方式:
待处理数据如下:
List<String> source = Arrays.asList("List", "Map", "Set", "Tree");
目标是按字符串长度分组,生成:
{
3: ["Map", "Set"],
4: ["List", "Tree"]
}
3.1. 使用 Collectors.groupingBy()
这是最直观的方法。Collectors.groupingBy
支持三个重载参数:
- 分类器(classifier)
- 自定义
Map
的Supplier
- 下游收集器(downstream collector),可嵌套自定义
List
的Supplier
代码实现:
public Map<Integer, List<String>> groupingByStringLength(
List<String> source,
Supplier<Map<Integer, List<String>>> mapSupplier,
Supplier<List<String>> listSupplier) {
return source.stream()
.collect(Collectors.groupingBy(
String::length, // classifier
mapSupplier, // map factory
Collectors.toCollection(listSupplier) // value collection factory
));
}
✅ 测试验证:
Map<Integer, List<String>> convertedMap =
converter.groupingByStringLength(source, HashMap::new, ArrayList::new);
assertTrue(convertedMap.get(3).contains("Map"));
assertTrue(convertedMap.get(4).contains("Tree"));
📌 优点:简洁明了,专为分组设计。
⚠️ 注意:下游必须使用 Collectors.toCollection(supplier)
才能传入 List
工厂。
3.2. 使用 Collectors.toMap()
toMap
通常用于一对一映射,但通过巧妙构造 value 和 merge 函数,也能实现 Map<K, List<V>>
。
步骤拆解:
- 定义 key 映射函数(字符串长度)
- 定义 value 映射函数(每个元素生成一个单元素列表)
- 定义合并函数(两个列表合并)
- 指定自定义
Map
的Supplier
完整实现:
public Map<Integer, List<String>> collectorToMapByStringLength(
List<String> source,
Supplier<Map<Integer, List<String>>> mapSupplier,
Supplier<List<String>> listSupplier) {
Function<String, Integer> keyMapper = String::length;
Function<String, List<String>> valueMapper = (element) -> {
List<String> collection = listSupplier.get();
collection.add(element);
return collection;
};
BinaryOperator<List<String>> mergeFunction = (existing, replacement) -> {
existing.addAll(replacement);
return existing;
};
return source.stream()
.collect(Collectors.toMap(keyMapper, valueMapper, mergeFunction, mapSupplier));
}
✅ 测试验证:
Map<Integer, List<String>> convertedMap =
converter.collectorToMapByStringLength(source, HashMap::new, ArrayList::new);
assertTrue(convertedMap.get(3).contains("Set"));
📌 优点:灵活性高,适用于复杂映射逻辑。
❌ 缺点:代码较冗长,容易出错;不适合大规模分组场景。
3.3. 使用 Stream.collect(Supplier, BiConsumer, BiConsumer)
这是最底层、最灵活的方式,对应 Collector
的三个核心函数:
supplier
:创建空结果容器accumulator
:累加元素到结果中combiner
:合并两个部分结果(并行流用)
实现如下:
public Map<Integer, List<String>> streamCollectByStringLength(
List<String> source,
Supplier<Map<Integer, List<String>>> mapSupplier,
Supplier<List<String>> listSupplier) {
BiConsumer<Map<Integer, List<String>>, String> accumulator = (response, element) -> {
Integer key = element.length();
List<String> values = response.getOrDefault(key, listSupplier.get());
values.add(element);
response.put(key, values);
};
BiConsumer<Map<Integer, List<String>, Map<Integer, List<String>>> combiner = (res1, res2) -> {
res1.putAll(res2);
};
return source.stream().collect(mapSupplier, accumulator, combiner);
}
✅ 测试验证:
Map<Integer, List<String>> convertedMap =
converter.streamCollectByStringLength(source, HashMap::new, ArrayList::new);
assertTrue(convertedMap.get(4).contains("List"));
📌 优点:完全掌控逻辑,适合高度定制化场景。
⚠️ 注意:combiner
在串行流中不会执行,但写并行流时必须正确实现。
4. 总结
三种方式对比:
方法 | 是否支持自定义 Map | 是否支持自定义 List | 推荐场景 |
---|---|---|---|
groupingBy |
✅ | ✅(via toCollection ) |
分组首选,简洁安全 |
toMap |
✅ | ✅ | 一对一或需自定义 merge 逻辑 |
stream.collect |
✅ | ✅ | 复杂聚合、并行流定制 |
✅ 最佳实践建议:
- 日常开发优先使用
Collectors.groupingBy
- 需要线程安全容器?试试
ConcurrentHashMap::new
+CopyOnWriteArrayList::new
- 别忘了
LinkedHashMap::new
保序需求
完整示例代码已托管至 GitHub:
👉 https://github.com/baeldung/java-tutorials/tree/master/core-java-modules/core-java-collections-conversions-2