手动链接GroupBy收藏家

Tho*_* Z. 18 java grouping java-8 java-stream collectors

我想把一个人的名单分组.一个人有一些属性,如姓名,国家,城镇,邮政编码等.我写了静态代码,非常有效:

Object groupedData = data.stream().collect(groupingBy(Person::getName, Collectors.groupingBy(Person::getCountry, Collectors.groupingBy(Person::getTown))));
Run Code Online (Sandbox Code Playgroud)

但问题是,这不是动态的.有时我想按名称和城镇分组,有时候按属性分组.我怎样才能做到这一点?非Java 8解决方案也是受欢迎的.

tob*_*s_k 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);
}
Run Code Online (Sandbox Code Playgroud)

请注意,grouper函数的顺序是相反的,因此您必须以相反的顺序传递它们(或者在函数内部反转它们,例如使用Collections.reverse或Guava的Lists.reverse,无论您喜欢哪个).

Object groupedData = collectMany(data, Person::getTown, Person::getCountry, Person::getName);
Run Code Online (Sandbox Code Playgroud)

或者像这样,使用旧式for循环来反转函数中的数组,即你不必以相反的顺序传递石斑鱼(但恕我直言,这是更难理解):

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);
}
Run Code Online (Sandbox Code Playgroud)

两种方法都将返回a Map<?,Map<?,Map<?,T>>>,就像在原始代码中一样.根据你想要对这些数据做什么Map<List<?>,T>,根据Tunaki的建议,使用a也可能值得考虑.


Tun*_*aki 8

您可以按照要分组的属性组成的列表进行分组.

想象一下,你想按名称和国家分组.然后你可以使用:

Map<List<Object>, List<Person>> groupedData = 
    data.stream().collect(groupingBy(p -> Arrays.asList(p.getName(), p.getCountry())));
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为当两个列表以相同的顺序包含相同的元素时,它们被认为是相等的.因此,在生成的地图中,您将拥有每个不同名称/国家/地区对的关键字以及具有这些特定名称和国家/地区的人员列表.换句话说,它不是说"按名称分组,然后按国家分组",而是有效地说"按名称和国家分组".优点是你不会最终得到地图地图等的地图.

  • 那个要求不是@tobias_k问题的一部分.只有键值的地图更容易使用具有未知内部地图级别的地图. (2认同)