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

Java 8 Map & Stream - Sorting by value desc and group

我需要在 Java 8.

中分组后,按计数总和对响应数据进行降序排序

我的视图 table 查询结果如下:

计数(bigint) 类别(varchar) 我的枚举(整数)
10 一个 0
35 B 0
30 一个 1
25 C 1

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

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
} 

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

{
  "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;
    }

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

您可以 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 class 中添加一个额外的方法:

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