TLDR:
要通过键 (Map<MyKey,MyObject>) 收集到包含单个值的 Map,请使用 Collectors.toMap()。
要通过键 (Map<MyKey, List<MyObject>>) 收集到包含多个值的 Map,请使用 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>,其中K 和U 是传递给该方法的两个函数的返回类型。
在您的情况下,Point::getParentId 是 Long,c 指的是 Point。
而Map<Long,Point> 在应用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<? 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() 绝对不是。