【问题标题】:How to make a Map of Map using Collectors如何使用收集器制作地图地图
【发布时间】:2019-10-12 05:50:28
【问题描述】:

我无法使用 Java 函数式编程 进行 地图构建。 在我的用例中,这只是一个示例,我想从 List<Person> 开始构建一个 Map<Integer, Map<Integer, 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]]

现在我想添加一个新的分组级别。我选择了 BirthDate 的年份,这是 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 of 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]]}}

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

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

【问题讨论】:

标签: java functional-programming collectors


【解决方案1】:

使用嵌套的groupingBy:

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

【讨论】:

  • 非常感谢@eran,它工作得很好!我真的不认为嵌套 groupingBy 会取代所有通常的构建方式。我的意思是:映射和 toList 去哪里了??
  • @Lovegiver 你不需要mappingtoList,因为groupingBy默认组合成List,而内部List的值仍然是Person,所以无需将其映射到其他东西。
  • 你是对的@eran,只是使用 groupingBy 语句会产生与 groupinBy + mapping + toList() 完全相同的结果。那么我们为什么要这样做呢? Oracle 文档最后一点都不清楚。
  • @Lovegiver 你需要将 mappingtoList 链接到 groupingBy 如果你想生成 Map&lt;Integer,Map&lt;Integer,List&lt;Person&gt;&gt;&gt; 而不是 Map&lt;Integer,Map&lt;Integer,List&lt;String&gt;&gt;&gt; (其中字符串是该人的姓名)。
猜你喜欢
  • 1970-01-01
  • 2017-05-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-18
  • 2017-06-08
  • 1970-01-01
相关资源
最近更新 更多