如何使用收藏家制作地图

时间:2019-05-26 10:36:46

标签: java functional-programming collectors

我被禁止使用 Java功能编程进行地图构建。 在我的用例中,这只是一个例子,我想从Map<Integer, Map<Integer, List<Person>>>开始构建List<Person>

第一步很简单:我建立了第一个Map<Integer, List<person>>

然后我要添加新的分组级别,以便生成新的地图:Map<Integer, Map<Integer, List<person>>>

重点是:如何正确使用所有收集器功能(groupingBy,映射或其他功能)添加新的分组级别? 我所有的尝试都失败了:编译器总是告诉我,我提供的所有功能都不符合预期。

这是代码,简短地:

首先是一个Person类,然后是一种快速构建List的方法:

public class Person {

  private long id;
  private String firstName;
  private String lastName;
  private Date birthDate;
  private int alea;
  ...

此方法(位于DataBuilder类中)仅构建一个Person列表以向Main类提供数据:

public List<Person> getPersons() {

        List<Person> persons = new ArrayList<>();
        Supplier<Person> person = Person::new;

        for (int cpt = 0; cpt < 10; cpt++) {
            Person p = person.get();
            p.setId(cpt);
            p.setFirstName("fn" + cpt);
            p.setLastName("ln" + cpt);
            p.setBirthDate(new Date(119, cpt, 10 + cpt));
            p.setAlea(cpt % 2);
            persons.add(p);
        }

        return persons;

    }

在Main中,构建Map的第一级不是问题:

public static void main(String[] args) {

    DataBuilder db = new DataBuilder();

    List<Person> persons = db.getPersons();

    Map<Integer, List<Person>> mapAleaPerson = persons.stream()
                .collect(Collectors.groupingBy(Person::getAlea,
                        Collectors.mapping(p -> p, Collectors.toList())));

        mapAleaPerson.forEach((k,v) -> System.out.printf("%n%s contains %s%n", k, v));

在控制台中结果正常:

0 contains [Person [id=0, firstName=fn0, lastName=ln0, alea=0], Person [id=2, firstName=fn2, lastName=ln2, alea=0], Person [id=4, firstName=fn4, lastName=ln4, alea=0], Person [id=6, firstName=fn6, lastName=ln6, alea=0], Person [id=8, firstName=fn8, lastName=ln8, alea=0]]

1 contains [Person [id=1, firstName=fn1, lastName=ln1, alea=1], Person [id=3, firstName=fn3, lastName=ln3, alea=1], Person [id=5, firstName=fn5, lastName=ln5, alea=1], Person [id=7, firstName=fn7, lastName=ln7, alea=1], Person [id=9, firstName=fn9, lastName=ln9, alea=1]]

现在,我想添加一个新的分组级别。我选择了出生日期的年份,这是Person对象的另一个属性。

基本上,我尝试过这样的事情:

Map<Integer,Map<Integer,List<Person>>> mapByYearThenAleaPerson = persons.stream()
                .collect(Collectors.groupingBy(p -> p.getBirthDate().getYear(),
                        Collectors.toMap(Collectors.groupingBy(Person::getAlea,
                        Collectors.mapping(p -> p, Collectors.toList())))));

铸造无济于事:

@SuppressWarnings("unchecked")
        Map<Integer,Map<Integer,List<Person>>> mapByYearThenAleaPerson = persons.stream()
                .collect(Collectors.groupingBy(p -> p.getBirthDate().getYear(),
                        Collectors.toMap((Function<Integer, List<Person>>) Collectors.groupingBy(Person::getAlea,
                        Collectors.mapping(p -> p, Collectors.toList())))));

我还尝试了Collectors.mapping而不是Collectors.toMap:

Map<Integer,Map<Integer,List<Person>>> mapByYearThenAleaPerson = persons.stream()
                .collect(Collectors.groupingBy(p -> p.getBirthDate().getYear(),
                        Collectors.mapping((Function<Integer, List<Person>>) Collectors.groupingBy(Person::getAlea,
                        Collectors.mapping(p -> p, Collectors.toList())))));

由于不兼容的函数类型,每次编译器返回错误。

我发现添加新分组级别的唯一方法是使用旧Java代码以及外部循环在Map Map中添加和分组数据。

在老式Java中嵌入FP很令人失望!

有没有人想使用纯FP来完成所有这些工作?


在@eran回答之后(效果很好),这是我所做的事情及其结果:

Map<Integer, Map<Integer, List<Person>>> mapByYearThenAleaPerson = persons.stream().collect(
                Collectors.groupingBy(p -> p.getBirthDate().getYear() + 1900, Collectors.groupingBy(Person::getAlea)));


{2018={0=[Person [id=0, firstName=fn0, lastName=ln0, alea=0], Person [id=2, firstName=fn2, lastName=ln2, alea=0], Person [id=4, firstName=fn4, lastName=ln4, alea=0]], 1=[Person [id=1, firstName=fn1, lastName=ln1, alea=1], Person [id=3, firstName=fn3, lastName=ln3, alea=1]]}, 2019={0=[Person [id=6, firstName=fn6, lastName=ln6, alea=0], Person [id=8, firstName=fn8, lastName=ln8, alea=0]], 1=[Person [id=5, firstName=fn5, lastName=ln5, alea=1], Person [id=7, firstName=fn7, lastName=ln7, alea=1], Person [id=9, firstName=fn9, lastName=ln9, alea=1]]}}

真正令人惊讶的是,与构建第一个Map本身的方式相比,嵌套groupingBy语句的简单性。

为什么我们不再需要指定mappingCollectors.toList()

1 个答案:

答案 0 :(得分:4)

使用嵌套的db.col.aggregate([ { $addFields: { expected: { $let: { vars: { parts: { $split: [ "$expected", ":" ] } }, in: { $sum: [ { $toInt: { $arrayElemAt: [ "$$parts", 2 ] } }, { $multiply: [ 60, { $toInt: { $arrayElemAt: [ "$$parts", 1 ] } } ] }, { $multiply: [ 3600, { $toInt: { $arrayElemAt: [ "$$parts", 0 ] } } ] } ] } } }, actual: { $let: { vars: { parts: { $split: [ "$actual", ":" ] } }, in: { $sum: [ { $toInt: { $arrayElemAt: [ "$$parts", 2 ] } }, { $multiply: [ 60, { $toInt: { $arrayElemAt: [ "$$parts", 1 ] } } ] }, { $multiply: [ 3600, { $toInt: { $arrayElemAt: [ "$$parts", 0 ] } } ] } ] } } } } }, { $match: { $expr: { $gt: [ "$expected", "$actual" ] } } } ])

groupingBy