如何使用Collectors.groupingBy创建嵌套Map?

Vin*_*ati 9 java java-8 java-stream

我有一个班级列表说 ProductDto

public class ProductDto {
    private String Id;
    private String status;
    private Booker booker;
    private String category;
    private String type;
}
Run Code Online (Sandbox Code Playgroud)

我想要一张地图如下: -

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;
                        })
                ));
Run Code Online (Sandbox Code Playgroud)

如果有人知道通过使用流来做到这一点的好方法,请分享!

Fed*_*ner 20

摘要/简要讨论

从面向对象的角度看,有一张地图地图的地图是有问题的,因为你可能看起来缺少一些抽象(即你可以创建一个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())))));
Run Code Online (Sandbox Code Playgroud)

如你所见,结果是一个Map<String, Map<String, Map<String, List<Booker>>>>.这是因为可能存在多个ProductDto具有相同(status, category, type)组合的实例.

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


关于减少

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


面向对象的友好方法

对于第二种方法,有Map<String, Map<String, Map<String, List<Booker>>>>可能被视为不良做法甚至是纯粹的邪恶.因此,您可以只拥有一个列表映射,而不是拥有列表映射映射的映射,其中的键表示要分组的3个属性的组合.

最简单的方法是使用a 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())))));
Run Code Online (Sandbox Code Playgroud)

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

  • 如果我看到地图地图的地图,我会责怪并瞪着谁负责任. (6认同)
  • 真棒!很好的答案:)欢迎替代品:) (3认同)