Ответ 1
Вы можете создать функцию с произвольным числом атрибутов для группировки и построения 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
, чтобы отменить массив в функции, т.е. вам не нужно передавать группы в обратном порядке (но имхо это сложнее понять):
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>>>
, как и в исходном коде. В зависимости от того, что вы хотите делать с этими данными, также стоит подумать об использовании Map<List<?>,T>
, как предложено Tunaki.