You could create a function taking an arbitrary number of attributes to group by and construct the groupingBy
-Collector
in a loop, each time passing the previous version of itself as the downstream
collector.
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);
}
Note that the order of the grouper
functions is reversed, so you have to pass them in reversed order (or reverse them inside the function, e.g. using Collections.reverse
or Guava's Lists.reverse
, whichever you prefer).
Object groupedData = collectMany(data, Person::getTown, Person::getCountry, Person::getName);
Or like this, using an old-school for
loop to reverse the array in the function, i.e. you don't have to pass the groupers in inverse order (but IMHO this is harder to comprehend):
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);
}
Both approaches will return a Map<?,Map<?,Map<?,T>>>
, just as in your original code. Depending on what you want to do with that data it might also be worth considering using a Map<List<?>,T>
, as suggested by Tunaki.