【问题标题】:How to create a nested Map using Collectors.groupingBy?如何使用 Collectors.groupingBy 创建嵌套地图?
【发布时间】:2018-02-07 13:29:39
【问题描述】:

我有一个班级列表说ProductDto

public class ProductDto {
    private String Id;
    private String status;
    private Booker booker;
    private String category;
    private String type;
}

我想要一张如下图:-

Map<String,Map<String,Map<String,Booker>>

属性映射如下:

Map<status,Map<category,Map<type,Booker>

我知道使用Collectors.groupingBy 可以轻松完成一级分组,没有任何麻烦。

我尝试将它用于嵌套级别,但是当作为键的字段开始出现相同的值时,它对我来说失败了。

我的代码如下:-

 list.stream()
                .collect(Collectors.groupingBy(
                        (FenergoProductDto productDto) -> 
                        productDto.getStatus()
                        ,
                        Collectors.toMap(k -> k.getProductCategory(), fProductDto -> {
                            Map<String, Booker> productTypeMap = new ProductTypes();
                            productTypeMap.put(fProductDto.getProductTypeName(),
                                    createBooker(fProductDto.getBookingEntityName()));
                            return productTypeMap;
                        })
                ));

如果有人知道使用流的好方法,请分享!

【问题讨论】:

  • 你需要给出一个reducing函数来指示在发生key冲突时需要做什么
  • 你能在这里发布一些有用的东西或任何参考链接等吗?

标签: java java-8 java-stream


【解决方案1】:

摘要/简要讨论

从面向对象的角度来看,拥有地图的地图是有问题的,因为您似乎缺少一些抽象(即您可以创建一个封装嵌套分组结果的类 Result )。但是,仅从纯粹的面向数据的方法考虑时,它是完全合理的。

所以在这里我提出了两种方法:第一种是纯粹面向数据的(使用嵌套的groupingBy 调用,因此嵌套了映射),而第二种方法对 OO 更友好,并且在抽象分组标准方面做得更好.只需选择一个更能代表您的意图和编码标准/传统,更重要的是,选择您最喜欢的那个。


面向数据的方法

对于第一种方法,您可以嵌套 groupingBy 调用:

Map<String, Map<String, Map<String, List<Booker>>>> result = list.stream()
    .collect(Collectors.groupingBy(ProductDto::getStatus,
             Collectors.groupingBy(ProductDto::getCategory,
             Collectors.groupingBy(ProductDto::getType,
                 Collectors.mapping(
                         ProductDto::getBooker,
                         Collectors.toList())))));

如您所见,结果是Map&lt;String, Map&lt;String, Map&lt;String, List&lt;Booker&gt;&gt;&gt;&gt;。这是因为可能有多个ProductDto 实例具有相同的(status, category, type) 组合。

另外,由于您需要 Booker 实例而不是 ProductDto 实例,我正在调整最后一个 groupingBy 收集器,以便它返回 Bookers 而不是 productDtos .


关于减少

如果您只需要一个 Booker 实例而不是 List&lt;Booker&gt; 作为最内层映射的值,则需要一种方法来减少 Booker 实例,即转换多个通过关联操作将实例合并为一个(累积某些属性的总和是最常见的)。


面向对象的友好方法

对于第二种方法,拥有Map&lt;String, Map&lt;String, Map&lt;String, List&lt;Booker&gt;&gt;&gt;&gt; 可能被视为不好的做法,甚至是纯粹的邪恶。因此,您可以只有一个列表映射,其键代表您要分组的 3 个属性的组合,而不是拥有列表映射的映射。

最简单的方法是使用List 作为键,因为列表已经提供了hashCodeequals 实现:

Map<List<String>, List<Booker>> result = list.stream()
    .collect(Collectors.groupingBy(
         dto -> Arrays.asList(dto.getStatus(), dto.getCategory(), dto.getType()),
         Collectors.mapping(
                 ProductDto::getBooker,
                 Collectors.toList())))));

如果您使用的是 Java 9+,则可以使用 List.of 而不是 Arrays.asList,因为 List.of 返回一个完全不可变且高度优化的列表。

【讨论】:

  • 太棒了!很好的答案:)也欢迎替代品:)
  • 只是一个简单的疑问!假设 ProductDto::getBooker 有 3 个属性 a、b 和 c 我们需要填充这些属性而不是将它们收集到一个列表中。怎么可能实现?
  • 如果我看到地图的地图,我会责备并瞪着谁负责。
  • @Michael 嵌套地图在特定情况下是完全合法的。在我们公司,我们使用多级地图来实现快速访问和低内存占用的数据投影
  • @epol 我没有说所有嵌套地图。我说的是 3 个(或更多)级别。它们太难以推理并且难以使用,尤其是当键是相同类型时。写一些不可读的东西很少有性能上的理由。在维护多个索引时,几乎总有比疯狂使用地图更好的策略。 cqengine 不错
猜你喜欢
  • 2021-06-16
  • 1970-01-01
  • 1970-01-01
  • 2014-10-18
  • 2020-04-26
  • 2021-07-10
  • 2018-06-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多