聚合 Java 中的对象列表

我们在Java中是否有任何聚合器函数来执行以下聚合?

Person {
    String name;
    String subject;
    String department;
    Long mark1;
    Long mark2;
    Long mark3;
}

列表包含如下数据。

Name    |Subject    |Department |Mark1  |Mark2  |Mark3
--------|-----------|-----------|-------|-------|-----
Clark   |English    |DEP1       |7      |8      |6
Michel  |English    |DEP1       |6      |4      |7
Dave    |Maths      |DEP2       |3      |5      |6
Mario   |Maths      |DEP1       |9      |7      |8

聚合标准是 Subject & Dep。生成的对象需要是

Subject     |Department |Mark1  |Mark2  |Mark3
----------- |-----------|-------|-------|-----
English     |DEP1       |13     |12     |13
Maths       |DEP2       |3      |5      |6
Maths       |DEP1       |9      |7      |8

可以通过手动循环访问列表并创建聚合列表来实现此聚合。示例如下。

private static List<Person> getGrouped(List<Person> origList) {
    Map<String, Person> grpMap = new HashMap<String, Person>();

    for (Person person : origList) {
        String key = person.getDepartment() + person.getSubject();
        if (grpMap.containsKey(key)) {
            Person grpdPerson = grpMap.get(key);
            grpdPerson.setMark1(grpdPerson.getMark1() + person.getMark1());
            grpdPerson.setMark2(grpdPerson.getMark2() + person.getMark2());
            grpdPerson.setMark3(grpdPerson.getMark3() + person.getMark3());
        } else {
            grpMap.put(key, person);
        }
    }
    return new ArrayList<Person>(grpMap.values());
}

但是Java 8是否有任何我们可以利用的聚合函数或特性?


答案 1

您可以使用缩减。聚合 mark1 的示例如下所示。

public class Test {

    static class Person {
        Person(String name, String subject, String department, Long mark1, Long mark2, Long mark3) {
            this.name = name;
            this.subject = subject;
            this.department = department;
            this.mark1 = mark1;
            this.mark2 = mark2;
            this.mark3= mark3;
        }
            String name;
            String subject;
            String department;
            Long mark1;
            Long mark2;
            Long mark3;

            String group() {
                return subject+department;
            }

            Long getMark1() {
                return mark1;
            }
    }

      public static void main(String[] args)
      {
        List<Person> list = new ArrayList<Test.Person>();
        list.add(new Test.Person("Clark","English","DEP1",7l,8l,6l));
        list.add(new Test.Person("Michel","English","DEP1",6l,4l,7l));
        list.add(new Test.Person("Dave","Maths","DEP2",3l,5l,6l));
        list.add(new Test.Person("Mario","Maths","DEP1",9l,7l,8l));

        Map<String, Long> groups = list.stream().collect(Collectors.groupingBy(Person::group, Collectors.reducing(
                    0l, Person::getMark1, Long::sum)));

        //Or alternatively as suggested by Holger 
        Map<String, Long> groupsNew = list.stream().collect(Collectors.groupingBy(Person::group, Collectors.summingLong(Person::getMark1)));

        System.out.println(groups);

      }

}

仍在考虑通过单个函数生成输出。完成后将更新。


答案 2

在 JDK 中使用标准收集器,您可以像这样操作(假设创建了一个类):Tuple3<E1, E2, E3>

Map<String, Map<String, Tuple3<Long, Long, Long>>> res =
    persons.stream().collect(groupingBy(p -> p.subject,
                                        groupingBy(p -> p.department,
                                                   reducing(new Tuple3<>(0L, 0L, 0L), 
                                                            p -> new Tuple3<>(p.mark1, p.mark2, p.mark3), 
                                                            (t1, t2) -> new Tuple3<>(t1.e1 + t2.e1, t1.e2 + t2.e2, t1.e3 + t2.e3)))));

这将首先按主题对元素进行分组,然后按部门对元素进行分组,并通过对其标记求和来减少第二个地图中的结果值。

在示例中的人员列表上运行它,您将获得输出:

Maths => DEP2 => (3, 5, 6)
Maths => DEP1 => (9, 7, 8)
English => DEP1 => (13, 12, 13)

在这种情况下,您可能还希望使用收集器使用另一个变体。逻辑保持不变,映射值的函数将创建一个包含部门作为键的映射,并将学生的成绩作为值。合并函数将负责添加或更新映射。toMap

Map<String, Map<String, Tuple3<Long, Long, Long>>> res3 =
        persons.stream()
               .collect(toMap(p -> p.subject,
                              p -> {
                                  Map<String, Tuple3<Long, Long, Long>> value = new HashMap<>();
                                  value.put(p.department, new Tuple3<>(p.mark1, p.mark2, p.mark3));
                                  return value;
                              },
                              (v1, v2) -> {
                                   v2.forEach((k, v) -> v1.merge(k, v, (t1, t2) -> new Tuple3<>(t1.e1 + t2.e1, t1.e2 + t2.e2, t1.e3 + t2.e3)));
                                   return v1;
                              }
               ));

当然,您可以质疑这些解决方案的“美感”,也许您想引入自定义收集器或自定义类以使意图更加清晰。


推荐