Java 8使用自定义收集器进行分组?

12

我有以下的类。

class Person {

    String name;
    LocalDate birthday;
    Sex gender;
    String emailAddress;

    public int getAge() {
        return birthday.until(IsoChronology.INSTANCE.dateNow()).getYears();
    }

    public String getName() {
        return name;
    }
}

我希望能够按年龄分组,然后收集人名列表而不是 Person 对象本身,并在一个漂亮的 lambda 表达式中完成所有操作。

为了简化这一切,我将链接我的当前解决方案,它会存储按年龄分组的结果,然后迭代收集名称。

ArrayList<OtherPerson> members = new ArrayList<>();

members.add(new OtherPerson("Fred", IsoChronology.INSTANCE.date(1980, 6, 20), OtherPerson.Sex.MALE, "fred@example.com"));
members.add(new OtherPerson("Jane", IsoChronology.INSTANCE.date(1990, 7, 15), OtherPerson.Sex.FEMALE, "jane@example.com"));
members.add(new OtherPerson("Mark", IsoChronology.INSTANCE.date(1990, 7, 15), OtherPerson.Sex.MALE, "mark@example.com"));
members.add(new OtherPerson("George", IsoChronology.INSTANCE.date(1991, 8, 13), OtherPerson.Sex.MALE, "george@example.com"));
members.add(new OtherPerson("Bob", IsoChronology.INSTANCE.date(2000, 9, 12), OtherPerson.Sex.MALE, "bob@example.com"));

Map<Integer, List<Person>> collect = members.stream().collect(groupingBy(Person::getAge));

Map<Integer, List<String>> result = new HashMap<>();

collect.keySet().forEach(key -> {
            result.put(key, collect.get(key).stream().map(Person::getName).collect(toList()));
});

当前解决方案

对于学习来说,这不是一个理想的解决方案。我希望有一个更加优雅和高效的解决方案。

3个回答

17

使用Collectors.groupingBy对Stream进行分组时,可以使用自定义的Collector对值进行约简操作。在这里,我们需要使用Collectors.mapping,它接受一个函数(用于映射)和一个收集器(如何收集映射后的值)。在本例中,映射是Person::getName,即返回人员名称的方法引用,并将其收集到一个List中。

Map<Integer, List<String>> collect = 
    members.stream()
           .collect(Collectors.groupingBy(
               Person::getAge,
               Collectors.mapping(Person::getName, Collectors.toList()))
           );

哎呀,这很容易啊!干杯! - lcardito
在这种情况下,只有String存在。如果成员有List<String>名称,该怎么办?并且,所有其他内容都相同。需要在Collectors.mapping方法内进行什么修改。还有一种方法可以在将对象修改为返回值之前修改它吗?例如,我需要修改Name One以执行某些自定义字符串操作。 - Satish Patro

3
您可以使用一个“mapping”收集器将“Person”列表映射为人名列表:
Map<Integer, List<String>> collect = 
    members.stream()
           .collect(Collectors.groupingBy(Person::getAge,
                                          Collectors.mapping(Person::getName, Collectors.toList())));

在这种情况下,只有String存在。如果成员有List<String>名称,该怎么办?并且,所有其他内容都相同。需要在Collectors.mapping方法内进行什么修改。还有任何方法可以在将对象修改为返回值之前修改它。例如,我需要修改Name One以执行一些自定义字符串操作。 - Satish Patro

2
你还可以使用Collectors.toMap,并提供键、值和合并函数(如果有)。
Map<Integer, String> ageNameMap = 
    members.stream()
            .collect(Collectors.toMap(
              person -> person.getAge(), 
              person -> person.getName(), (pName1, pName2) -> pName1+"|"+pName2)
    );

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接