如何使用 Collectors 制作 Map of Map
How to make a Map of Map using Collectors
我无法使用 Java 函数式编程 地图构建。
在我的用例中,这只是一个例子,我想从 List<Person>
.
开始构建一个 Map<Integer, Map<Integer, List<Person>>>
第一步很简单:我先构建一个 Map<Integer, List<person>>
。
然后我想添加一个新的分组级别,这样它将生成一个新的地图:Map<Integer, Map<Integer, List<person>>>
。
重点是:如何正确使用收集器的所有功能(groupingBy、映射,也许还有其他功能)来添加新的分组级别?
我所有的尝试都失败了:编译器总是告诉我我提供的所有函数都不符合它的预期。
这是代码,简要说明:
首先是一个人 class 然后是一个快速构建列表的方法 :
public class Person {
private long id;
private String firstName;
private String lastName;
private Date birthDate;
private int alea;
...
此方法(位于 DataBuilder class 中)仅构建一个 Person 列表以向 Main class 提供数据:
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())))));
每次编译器 returns 由于函数类型不兼容而出错。
我发现添加新分组级别的唯一方法是使用旧 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]]}}
真正令人惊讶的是,与构建第一个 Map 本身的方式相比,嵌套 groupingBy
语句的简单性。
为什么我们不再需要指定 mapping
和 Collectors.toList()
?
使用嵌套groupingBy
:
Map<Integer,Map<Integer,List<Person>>> mapByYearThenAleaPerson =
persons.stream()
.collect(Collectors.groupingBy(p -> p.getBirthDate().getYear(),
Collectors.groupingBy(Person::getAlea)));
我无法使用 Java 函数式编程 地图构建。
在我的用例中,这只是一个例子,我想从 List<Person>
.
Map<Integer, Map<Integer, List<Person>>>
第一步很简单:我先构建一个 Map<Integer, List<person>>
。
然后我想添加一个新的分组级别,这样它将生成一个新的地图:Map<Integer, Map<Integer, List<person>>>
。
重点是:如何正确使用收集器的所有功能(groupingBy、映射,也许还有其他功能)来添加新的分组级别? 我所有的尝试都失败了:编译器总是告诉我我提供的所有函数都不符合它的预期。
这是代码,简要说明:
首先是一个人 class 然后是一个快速构建列表的方法 :
public class Person {
private long id;
private String firstName;
private String lastName;
private Date birthDate;
private int alea;
...
此方法(位于 DataBuilder class 中)仅构建一个 Person 列表以向 Main class 提供数据:
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())))));
每次编译器 returns 由于函数类型不兼容而出错。
我发现添加新分组级别的唯一方法是使用旧 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]]}}
真正令人惊讶的是,与构建第一个 Map 本身的方式相比,嵌套 groupingBy
语句的简单性。
为什么我们不再需要指定 mapping
和 Collectors.toList()
?
使用嵌套groupingBy
:
Map<Integer,Map<Integer,List<Person>>> mapByYearThenAleaPerson =
persons.stream()
.collect(Collectors.groupingBy(p -> p.getBirthDate().getYear(),
Collectors.groupingBy(Person::getAlea)));