簡體   English   中英

Collectors.toMap() 和 Collectors.groupingBy() 收集到 Map 的區別

[英]Differences between Collectors.toMap() and Collectors.groupingBy() to collect into a Map

我想從Points List創建一個Map ,並在地圖中將列表中的所有條目映射到相同的 parentId,例如Map<Long, List<Point>>
我使用了Collectors.toMap()但它不能編譯:

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

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是 Long , c指的是Point Map<Long,Point>在應用collect()時返回。

正如Collectors.toMap() javadoc 所說,這種行為是相當預期的:

返回一個將元素累積到MapCollector ,其鍵和值是將提供的映射函數應用於輸入元素的結果。

但是如果映射的鍵包含重復項(根據Object.equals(Object) ),則會IllegalStateException
這可能是您的情況,因為您將根據特定屬性對Point分組: 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 (流的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()絕對不是。

Collectors.groupingBy正是您想要的,它從您的輸入集合創建一個 Map,使用您為其鍵提供的Function創建一個條目,以及一個帶有關聯鍵作為值的點列表。

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

下面的代碼做的東西。 Collectors.toList()是默認的,所以你可以跳過它,但如果你想要Map<Long, Set<Point>> Collectors.toSet()將需要。

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

通常情況下,從 object.field 到共享該字段的對象集合的映射最好存儲在 Multimap 中(Guava 有一個很好的 multimap 實現)。 如果您不需要 multimap 是可變的(這應該是所需的情況),您可以使用

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

如果您必須使用可變映射,您可以實現一個收集器(如下所示: https : //blog.jayway.com/2014/09/29/java-8-collector-for-gauvas-linkedhashmultimap/ )或使用一個 for 循環(或 forEach)來填充一個空的、可變的多映射。

當您使用從字段到共享字段的對象集合的映射(例如對象總數)時,多映射為您提供了通常需要的附加功能。

可變多映射還可以更輕松地向映射添加和刪除元素(無需考慮邊緣情況)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM