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

2022-01-22 00:00:00 java-8 java java-stream

我有一个类列表说 ProductDto

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

我想要一张如下图:-

地图>

属性映射如下:

地图

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

I know one level of grouping could be done easily without any hassles using Collectors.groupingBy.

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

I tried to use this for nested level but it failed for me when same values started coming for fields that are keys.

我的代码如下:-

 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;
                        })
                ));

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

If anyone knows a good approach to do this by using streams, please share!

推荐答案

摘要/简要讨论

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

Abstract / Brief discussion

Having a map of maps of maps is questionable when seen from an object-oriented prespective, as it might seem that you're lacking some abstraction (i.e. you could create a class Result that encapsulates the results of the nested grouping). However, it's perfectly reasonable when considered exclusively from a pure data-oriented approach.

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

So here I present two approaches: the first one is purely data-oriented (with nested groupingBy calls, hence nested maps), while the second one is more OO-friendly and makes a better job at abstracting the grouping criteria. Just pick the one which better represents your intentions and coding standards/traditions and, more importantly, the one you most like.

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

For the first approach, you can just nest the groupingBy calls:

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>>>.这是因为可能有多个 ProductDto 实例具有相同的 (status, category, type) 组合.

As you see, the result is a Map<String, Map<String, Map<String, List<Booker>>>>. This is because there might be more than one ProductDto instance with the same (status, category, type) combination.

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

Also, as you need Booker instances instead of ProductDto instances, I'm adapting the last groupingBy collector so that it returns Bookers instead of productDtos.

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

If you need to have only one Booker instance instead of a List<Booker> as the value of the innermost map, you would need a way to reduce Booker instances, i.e. convert many instances into one by means of an associative operation (accumulating the sum of some attribute being the most common one).

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

For the second approach, having a Map<String, Map<String, Map<String, List<Booker>>>> might be seen as bad practice or even as pure evil. So, instead of having a map of maps of maps of lists, you could have only one map of lists whose keys represent the combination of the 3 properties you want to group by.

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

The easiest way to do this is to use a List as the key, as lists already provide hashCode and equals implementations:

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 返回一个完全不可变且高度优化列表.

If you are on Java 9+, you can use List.of instead of Arrays.asList, as List.of returns a fully immutable and highly optimized list.

相关文章