1. 概述

本文将演示如何将 List<E> 转换为 Map<K, List<E>> 类型的结构。✅
核心实现依赖 Java 8 的 Stream API函数式接口 Supplier,通过自定义 Supplier 来控制中间集合的创建方式,比如使用 LinkedHashMapCopyOnWriteArrayList 等特定实现。

这种技巧在实际开发中非常实用,尤其是在需要定制集合类型(如线程安全、有序性)时,避免默认的 HashMapArrayList 带来的潜在问题。踩坑警告 ⚠️:别小看这个 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。我们重点关注以下三种支持自定义 MapList 创建逻辑的方式:

待处理数据如下:

List<String> source = Arrays.asList("List", "Map", "Set", "Tree");

目标是按字符串长度分组,生成:

{
    3: ["Map", "Set"],
    4: ["List", "Tree"]
}

3.1. 使用 Collectors.groupingBy()

这是最直观的方法。Collectors.groupingBy 支持三个重载参数:

  • 分类器(classifier)
  • 自定义 MapSupplier
  • 下游收集器(downstream collector),可嵌套自定义 ListSupplier

代码实现:

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>>

步骤拆解:

  1. 定义 key 映射函数(字符串长度)
  2. 定义 value 映射函数(每个元素生成一个单元素列表)
  3. 定义合并函数(两个列表合并)
  4. 指定自定义 MapSupplier

完整实现:

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


原始标题:Converting List to Map With a Custom Supplier | Baeldung