Collectors.toMap()和Collectors.groupingBy()之间的差异收集到Map中

时间:2017-07-21 07:15:01

标签: java java-8 collectors

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

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

4 个答案:

答案 0 :(得分:46)

TLDR:

要收集按键(Map)包含单个值的Map<MyKey,MyObject>,请使用Collectors.toMap()
要收集按键(Map)包含多个值的Map<MyKey, List<MyObject>>,请使用Collectors.groupingBy()

<强> Collectors.toMap()

写作:

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

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

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

返回Collector,结果为Map<K,U>,其中KU是传递给方法的两个函数的返回类型。 在您的情况下,Point::getParentId为长,cPoint。 而在Map<Long,Point>应用时collect()返回。

这种行为很可能是Collectors.toMap() javadoc状态:

  

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

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

如果映射的键可能有重复项,则可以使用toMap(Function, Function, BinaryOperator)重载,但它不会真正解决您的问题,因为它不会将具有相同parentId的元素分组。它只是提供了一种不具有相同public static <T, K> Collector<T, ?, Map<K, List<T>>> groupingBy(Function<? super T, ? extends K> classifier) 的两个元素的方法。

<强> Collectors.groupingBy()

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

Function

指定为:

  

通过&#34;返回实现&#34;组的收集器。输入操作   T类元素,根据分类对元素进行分组   函数,并在Map中返回结果。

该方法需要Function 在您的情况下,Point参数为type(Stream的Point.getParentId(),如果您想按parentId值对元素进行分组,则返回Map<Long, List<Point>> pointByParentId = chargePoints.stream() .collect(Collectors.groupingBy( p -> p.getParentId())); 。 / p>

所以你可以写:

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

或使用方法参考:

groupingBy()

Collectors.groupingBy():更进一步

确实Collectors.groupingBy(Function<? super T, ? extends K> classifier)收集器比实际例子更进一步。 Map方法最终只是将收集的List的值存储在Map中的便捷方法。
要将List的值存储在除groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, A, D> downstream)之外的其他内容中,或者要存储特定计算的结果,Map<Long, Set<Point>> pointByParentId = chargePoints.stream() .collect(Collectors.groupingBy(Point::getParentId, toSet())); 会让您感兴趣。

例如:

groupingBy()

除了问题之外,您应该将Map视为一种灵活的方式来选择要存储到收集的toMap()中的值,最终{{1}}不是。

答案 1 :(得分:3)

Collectors.groupingBy正是您想要的,它会根据您的输入集合创建一个地图,使用您为其提供密钥的Function创建一个条目,并使用您关联的密钥创建一个分数列表,因为它是值。

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

答案 2 :(得分:3)

以下代码完成了这些工作。 Collectors.toList()是默认值,因此您可以跳过它,但如果您想要Map<Long, Set<Point>> Collectors.toSet()则需要。

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

答案 3 :(得分:0)

从object.field到共享此字段的对象集合的映射通常更好地存储在Multimap中(Guava对于multimap有很好的实现)。 如果您不需要多图可变,则可以使用

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

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

多图为您提供了从字段到共享一个字段的对象集合(例如总对象数)的映射时通常需要的其他功能。

可变的多图还使添加和删除图元素变得更加容易(无需考虑边缘情况)。