我想列出一个人的列表。一个人有一些属性,如姓名,国家,城镇,邮政编码等。我写了静态代码,非常有效:
Object groupedData = data.stream().collect(groupingBy(Person::getName, Collectors.groupingBy(Person::getCountry, Collectors.groupingBy(Person::getTown))));
但问题是,那不是动态的。有时我想按名称和城镇分组,有时候按属性分组。我怎样才能做到这一点?非Java 8解决方案也受到欢迎。
答案 0 :(得分:11)
您可以创建一个函数,将任意数量的属性分组并在循环中构造groupingBy
- Collector
,每次都将其自身的先前版本作为downstream
传递收集器。
public static <T> Map collectMany(List<T> data, Function<T, ?>... groupers) {
Iterator<Function<T, ?>> iter = Arrays.asList(groupers).iterator();
Collector collector = Collectors.groupingBy(iter.next());
while (iter.hasNext()) {
collector = Collectors.groupingBy(iter.next(), collector);
}
return (Map) data.stream().collect(collector);
}
请注意,grouper
函数的顺序是相反的,因此您必须以相反的顺序传递它们(或者在函数内部反转它们,例如使用Collections.reverse
或Guava的Lists.reverse
,无论你喜欢哪种方式。)
Object groupedData = collectMany(data, Person::getTown, Person::getCountry, Person::getName);
或者像这样,使用旧学校for
循环来反转函数中的数组,即你不必按相反的顺序传递石斑鱼(但恕我直言,这更难以理解):< / p>
public static <T> Map collectMany(List<T> data, Function<T, ?>... groupers) {
Collector collector = Collectors.groupingBy(groupers[groupers.length-1]);
for (int i = groupers.length - 2; i >= 0; i--) {
collector = Collectors.groupingBy(groupers[i], collector);
}
return (Map) data.stream().collect(collector);
}
两种方法都会返回Map<?,Map<?,Map<?,T>>>
,就像在原始代码中一样。根据您对数据的处理方式,Tunaki建议使用Map<List<?>,T>
也可能值得考虑。
答案 1 :(得分:8)
您可以按照要分组的属性组成的列表进行分组。
想象一下,您希望按名称和国家/地区进行分组。然后你可以使用:
Map<List<Object>, List<Person>> groupedData =
data.stream().collect(groupingBy(p -> Arrays.asList(p.getName(), p.getCountry())));
这是有效的,因为当两个列表包含相同顺序的相同元素时,它们被认为是相等的。因此,在生成的地图中,您将拥有每个不同名称/国家/地区对的关键字以及具有这些特定名称和国家/地区的人员列表。换句话说,不是按名称分组,而是按国家/地区分组,而是按照名称和国家&#34;有效地说&#34;分组。优点是你不会最终得到地图地图等地图。