【问题标题】:Differences between Collectors.toMap() and Collectors.groupingBy() to collect into a MapCollectors.toMap() 和 Collectors.groupingBy() 收集成 Map 的区别
【发布时间】:2017-12-27 03:11:38
【问题描述】:

我想从ListPoints 创建一个Map,并在映射中将列表中的所有条目映射为相同的parentId,例如Map<Long, List<Point>>
我使用了Collectors.toMap(),但它没有编译:

Map<Long, List<Point>> pointByParentId = chargePoints.stream()
    .collect(Collectors.toMap(Point::getParentId, c -> c));

【问题讨论】:

  • 听起来你在找Collectors.groupingBy
  • 我同意 LouisWasserman 对问题的解释,但你的问题应该更清楚。提供一些示例输入和输出,甚至是Minimal, Complete, and Verifiable Example

标签: java java-8 collectors


【解决方案1】:

从 object.field 到共享该字段的对象 Collection 的映射通常更好地存储在 Multimap 中(Guava 有一个很好的 multimap 实现)。 如果您不需要多图是可变的(这应该是所需的情况),您可以使用

Multimaps.index(chargePoints, Point::getParentId);

如果您必须使用可变映射,您可以实现一个收集器(如此处所示:https://blog.jayway.com/2014/09/29/java-8-collector-for-gauvas-linkedhashmultimap/)或使用 for 循环(或 forEach)来填充一个空的可变多映射。

当您使用从字段到共享字段的对象集合的映射时,多映射为您提供了通常需要的额外功能(例如对象总数)。

可变多图还可以更轻松地向地图添加和删除元素(无需担心边缘情况)。

【讨论】:

    【解决方案2】:

    TLDR:

    要通过键 (Map&lt;MyKey,MyObject&gt;) 收集到包含单个值的 Map,请使用 Collectors.toMap()
    要通过键 (Map&lt;MyKey, List&lt;MyObject&gt;&gt;) 收集到包含多个值的 Map,请使用 Collectors.groupingBy()


    Collectors.toMap()

    通过写作:

    chargePoints.stream().collect(Collectors.toMap(Point::getParentId, c -> c));
    

    返回的对象将具有Map&lt;Long,Point&gt; 类型。
    查看您正在使用的Collectors.toMap() 函数:

    Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                        Function<? super T, ? extends U> valueMapper)
    

    它返回一个Collector,结果为Map&lt;K,U&gt;,其中KU 是传递给该方法的两个函数的返回类型。 在您的情况下,Point::getParentId 是 Long,c 指的是 Point。 而Map&lt;Long,Point&gt; 在应用collect() 时返回。

    正如Collectors.toMap() javadoc 所说,这种行为是意料之中的:

    返回一个Collector,它将元素累积到一个Map,其键和值 是将提供的映射函数应用于输入的结果 元素。

    但是如果映射的键包含重复项(根据Object.equals(Object)),则会抛出IllegalStateException
    这可能是您的情况,因为您将根据特定属性对Points 进行分组:parentId

    如果映射的键可能有重复,您可以使用toMap(Function, Function, BinaryOperator) 重载,但它不会真正解决您的问题,因为它不会将具有相同parentId 的元素分组。它只会提供一种方法,使两个元素不具有相同的parentId


    Collectors.groupingBy()

    为了达到您的要求,您应该使用Collectors.groupingBy(),它的行为和方法声明更适合您的需要:

    public static <T, K> Collector<T, ?, Map<K, List<T>>>
    groupingBy(Function<? super T, ? extends K> classifier) 
    

    指定为:

    返回一个对输入执行“分组依据”操作的收集器 T 类型的元素,根据分类对元素进行分组 函数,并在 Map 中返回结果。

    该方法采用Function
    在您的情况下,Function 参数是 Point(Stream 的 type)并且您返回 Point.getParentId(),因为您想按 parentId 值对元素进行分组。

    所以你可以写:

    Map<Long, List<Point>> pointByParentId = 
                           chargePoints.stream()
                                       .collect(Collectors.groupingBy( p -> p.getParentId())); 
    

    或者使用方法参考:

    Map<Long, List<Point>> pointByParentId = 
                           chargePoints.stream()
                                       .collect(Collectors.groupingBy(Point::getParentId));
    

    Collectors.groupingBy() :走得更远

    确实,groupingBy() 收集器比实际示例更进一步。 Collectors.groupingBy(Function&lt;? super T, ? extends K&gt; classifier) 方法最终只是一种方便的方法,可以将收集到的Map 的值存储在List 中。
    要将Map 的值存储在List 之外的其他事物中或存储特定计算的结果,groupingBy(Function&lt;? super T, ? extends K&gt; classifier, Collector&lt;? super T, A, D&gt; downstream) 应该会让您感兴趣。

    例如:

    Map<Long, Set<Point>> pointByParentId = 
                           chargePoints.stream()
                                       .collect(Collectors.groupingBy(Point::getParentId, toSet()));
    

    因此,除了所提出的问题之外,您应该考虑将groupingBy() 视为一种灵活的方式来选择要存储到收集的Map 中的值,而toMap() 绝对不是。

    【讨论】:

    • @TimSchwalbe on Stack Overflow 而不是说谢谢,我们赞成并接受答案。
    • 四处寻找有关 toMap() 的信息!打个招呼!
    【解决方案3】:

    下面的代码可以做这些事情。 Collectors.toList() 是默认的,所以你可以跳过它,但如果你想拥有Map&lt;Long, Set&lt;Point&gt;&gt; Collectors.toSet() 就需要了。

    Map<Long, List<Point>> map = pointList.stream()
                    .collect(Collectors.groupingBy(Point::getParentId, Collectors.toList()));
    

    【讨论】:

      【解决方案4】:

      Collectors.groupingBy 正是您想要的,它从您的输入集合创建一个 Map,使用您为其提供的 Function 创建一个条目作为它的键,并使用您的关联键作为它的值创建一个点列表。

      Map<Long, List<Point>> pointByParentId = chargePoints.stream()
          .collect(Collectors.groupingBy(Point::getParentId));
      
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-04-14
      • 2016-12-26
      • 2016-05-10
      • 2012-04-21
      • 2017-08-08
      • 2022-06-24
      • 2012-07-07
      相关资源
      最近更新 更多