Java 8 Map & Stream - 按值 desc 和组排序

Posted

技术标签:

【中文标题】Java 8 Map & Stream - 按值 desc 和组排序【英文标题】:Java 8 Map & Stream - Sorting by value desc and group 【发布时间】:2021-08-19 16:17:23 【问题描述】:

在Java 8中分组后,我需要按计数和对响应数据进行降序排序。

我有一个视图表查询结果,如:

count(bigint) category(varchar) myEnum(int)
10 A 0
35 B 0
30 A 1
25 C 1

我有一个视图表的投影界面,用于自定义 JPA 查询的结果。

public interface MyView 

    Long getCount();

    String getCategory();

    MyEnum getMyEnum();
 

这个是我的响应 DTO:

public class MyResponse 

    private List<Long> count = new ArrayList<>();

    private List<String> categories = new ArrayList<>();

    private List<List<MyEnum>> myEnums = new ArrayList<>();

    // ctors, getters and setters
 

我需要按类别对数据进行分组并对总计数求和,然后将 Enum 类型收集到每个类别的列表中。据此,类别 A 的计数应为 40,并且具有 0,1 个枚举类型。 因此,客户端需要在获取请求后得到如下结果:


  "count": [
    40,
    35,
    25
  ],
  "categories": [
    "A",
    "B",
    "C"
  ],
  "myEnums": [
    [
      "ENUM_A",
      "ENUM_B"
    ],
    [
      "ENUM_A",
    ],
    [
      "ENUM_B",
    ]
  ]

这是我服务中的相关功能:

    public MyResponse foo() 
        // This list have the list of MyView.
        List<MyView> myList = myRepository.getCountView());

        Map<String, List<MyView>> myMap = myList.stream().collect(Collectors.groupingBy(MyView::getCategory));

        MyResponse response = new MyResponse();

        myMap.forEach((key, value) -> 
                    response.getCategories().add(key);
                    response.getCount().add(value.stream().mapToLong(MyView::getCount).sum());
                    response.getMyEnums().add(value.stream().flatMap(v -> Stream.of(v.getMyEnum())).collect(Collectors.toList()));
                
        );

        return response;
    

好的,我已完成按类别分组并对计数求和,但无法对它们进行排序。结果是正确的,但我需要按总计数降序排列数据。 如果您有任何建议,我将不胜感激。谢谢!

【问题讨论】:

写一个 Function&lt;List&lt;MyView&gt;, MyResponse&gt; mapView 为单个视图列表创建响应,以及一个 BinaryOperator&lt;MyResponse&gt; merge 添加两个响应。然后,您可以使用它们来减少原始分组列表:myList.stream().collect(groupingBy(MyView::getCategory, reducing(MyResponse::new, mapView, merge)) 您可以使用.map(v -&gt; v.getMyEnum()).map(MyView::getMyEnum) 而不是.flatMap(v -&gt; Stream.of(v.getMyEnum()))。然后,将myMap.forEach((key, value) -&gt; … 替换为myMap.entrySet().stream().sorted(Comparator.comparingLong(e -&gt; e.getValue().getCount())).forEach(e -&gt; … 之类的东西,不幸的是,这意味着您必须手动在消费者中提取e.getKey() 任何e.getValue() 感谢您的建议。根据@Most Needed Rabbit 的回答,我解决了这个问题。另外,我申请了.map(v -&gt; v.getMyEnum()) or .map(MyView::getMyEnum),而不是.flatMap(v -&gt; Stream.of(v.getMyEnum())) 【参考方案1】:

你可以将map映射中的每个条目变成一个CategorySummary,然后缩减为一个MyResponse

List<MyView> myList = Arrays.asList(
        new MyView(30, "B", MyEnum.ENUM_B),
        new MyView(10, "A", MyEnum.ENUM_A),
        new MyView(35, "B", MyEnum.ENUM_A),
        new MyView(25, "C", MyEnum.ENUM_B)
);

Map<String, List<MyView>> myMap = myList.stream()
        .collect(Collectors.groupingBy(MyView::getCategory, TreeMap::new, Collectors.toList()));

MyResponse myResponse = myMap.entrySet().stream()
        .map(e -> new CategorySummary(
                e.getValue().stream().mapToLong(MyView::getCount).sum(),
                e.getKey(),
                e.getValue().stream().flatMap(v -> Stream.of(v.getMyEnum())).collect(Collectors.toList())
        ))
        .sorted(Comparator.comparing(CategorySummary::getCount).reversed())
        .reduce(new MyResponse(),
                (r, category) -> 
                    r.addCategory(category);
                    return r;
                , (myResponse1, myResponse2) ->  // the combiner won't be invoked because it's sequential stream.
                    myResponse1.getMyEnums().addAll(myResponse2.getMyEnums());
                    myResponse1.getCount().addAll(myResponse2.getCount());
                    myResponse1.getCategories().addAll(myResponse2.getCategories());
                    return myResponse1;
                );

System.out.println(new ObjectMapper().writeValueAsString(myResponse));

输出:


    "count": [65, 25, 10],
    "categories": ["B", "C", "A"],
    "myEnums": [
        ["ENUM_B", "ENUM_A"],
        ["ENUM_B"],
        ["ENUM_A"]
    ]


CategorySummary:

@AllArgsConstructor
@Getter
public class CategorySummary 
    private long count;
    private String name;
    private List<MyEnum> myEnums;

我还必须在MyResponse 类中添加一个额外的方法:

public void addCategory(CategorySummary categorySummary)
    this.count.add(categorySummary.getCount());
    this.categories.add(categorySummary.getName());
    this.myEnums.add(categorySummary.getMyEnums());

【讨论】:

嘿@Most Needed Rabbit,感谢您的回答。这个解决方案完全适合。只是担心后端的成本。您认为这是最佳做法还是在客户端排序更好? @hayriaral 性能方面,我认为最好是做 BE,因为 Java 更快。我认为还有一件事需要考虑,这取决于界面。作为客户端。您希望对数据进行排序,还是只是为了以将来可能会更改的特定方式向用户呈现信息?如果 UI 发生变化,它不应影响从 BE 发送的数据。 发送的数据已经为其他情况服务,排序不是问题。就在这种情况下,我需要对数据进行排序,感谢您的解决方案。

以上是关于Java 8 Map & Stream - 按值 desc 和组排序的主要内容,如果未能解决你的问题,请参考以下文章

java 8 Stream(流)

Java 8 中的 map() 和 flatMap() 方法有啥区别?

怎么在java 8的map中使用stream

怎么在java 8的map中使用stream

怎么在java 8的map中使用stream

Java 8 - 在 stream.map() 中链接构造函数调用和设置器