1. Вступление
В этой статье мы увидим, как работает сборщик groupingBy , используя различные примеры.
Чтобы понять материал, описанный в этой статье, необходимы базовые знания функций Java 8. Вы можете взглянуть на ссылку:/java-8-streams-введение[введение в потоки Java 8]и ссылку:/java-8-collectors[руководство по сборщикам Java 8].
2. GroupingBy Collectors
Java 8 Stream API позволяет нам обрабатывать коллекции данных декларативным способом.
Статические методы фабрики Collectors.groupingBy () и Collectors.groupingByConcurrent () предоставляют нам функциональность, аналогичную предложению « GROUP BY» в языке SQL. Они используются для группировки объектов по некоторым свойствам и сохранения результатов в экземпляре Map__.
Перегруженные методы groupingBy :
**
static <T,K> Collector<T,?,Map<K,List<T>>>
groupingBy(Function<? super T,? extends K> classifier)
-
С функцией классификации и вторым коллектором в качестве метода
параметры:
static <T,K,A,D> Collector<T,?,Map<K,D>>
groupingBy(Function<? super T,? extends K> classifier,
Collector<? super T,A,D> downstream)
-
С помощью функции классификации - метод поставщика (который обеспечивает
Map реализация, которая будет содержать конечный результат), и второй сборщик в качестве параметров метода:
static <T,K,D,A,M extends Map<K,D>> Collector<T,?,M>
groupingBy(Function<? super T,? extends K> classifier,
Supplier<M> mapFactory, Collector<? super T,A,D> downstream)
2.1. Пример настройки кода
Чтобы продемонстрировать использование groupingBy (), давайте определим класс BlogPost (мы будем использовать поток объектов BlogPost ):
class BlogPost {
String title;
String author;
BlogPostType type;
int likes;
}
BlogPostType :
enum BlogPostType {
NEWS,
REVIEW,
GUIDE
}
List из BlogPost объектов:
List<BlogPost> posts = Arrays.asList( ... );
Давайте также определим класс Tuple , который будет использоваться для группировки сообщений по комбинации их атрибутов type и author :
class Tuple {
BlogPostType type;
String author;
}
2.2. Простая группировка по одному столбцу
Начнем с самого простого метода groupingBy , который принимает в качестве параметра только функцию классификации. Функция классификации применяется к каждому элементу потока. Значение, возвращаемое функцией, используется как ключ к карте, которую мы получаем от сборщика groupingBy
Чтобы сгруппировать записи блога в списке постов блога по их типу:
Map<BlogPostType, List<BlogPost>> postsPerType = posts.stream()
.collect(groupingBy(BlogPost::getType));
2.3. Группировка по сложному типу ключа Map
Функция классификации не ограничивается возвратом только скалярного или строкового значения. Ключом полученной карты может быть любой объект, если мы обеспечиваем реализацию необходимых методов equals и hashcode .
Чтобы сгруппировать по сообщениям блога в списке по type и author , объединенным в экземпляр Tuple :
Map<Tuple, List<BlogPost>> postsPerTypeAndAuthor = posts.stream()
.collect(groupingBy(post -> new Tuple(post.getType(), post.getAuthor())));
2.4. Изменение типа возвращаемого значения Map
Вторая перегрузка groupingBy требует дополнительного второго коллектора (нисходящего коллектора), который применяется к результатам первого коллектора.
Когда мы указываем только функцию классификации, а не нижестоящий коллектор, за кулисами используется коллектор toList () .
Давайте использовать toSet () сборщик в качестве нижестоящего сборщика и получить Set постов в блоге (вместо List ):
Map<BlogPostType, Set<BlogPost>> postsPerType = posts.stream()
.collect(groupingBy(BlogPost::getType, toSet()));
2.5. Предоставление Вторичной Группы Коллектором
Другое применение нижестоящего коллектора - выполнить вторичную группировку по результатам первой группы по.
Чтобы сгруппировать List из _BlogPost s сначала по author , а затем по type_ :
Map<String, Map<BlogPostType, List>> map = posts.stream()
.collect(groupingBy(BlogPost::getAuthor, groupingBy(BlogPost::getType)));
2.6. Получение среднего из сгруппированных результатов
Используя нижестоящий коллектор, мы можем применять функции агрегирования в результатах функции классификации.
Чтобы найти среднее количество likes для каждого сообщения в блоге type :
Map<BlogPostType, Double> averageLikesPerType = posts.stream()
.collect(groupingBy(BlogPost::getType, averagingInt(BlogPost::getLikes)));
2.7. Получение суммы из сгруппированных результатов
Чтобы рассчитать общую сумму likes для каждого type :
Map<BlogPostType, Integer> likesPerType = posts.stream()
.collect(groupingBy(BlogPost::getType, summingInt(BlogPost::getLikes)));
2.8. Получение максимума или минимума из сгруппированных результатов
Еще одна агрегация, которую мы можем выполнить, - получить пост в блоге с максимальным количеством лайков:
Map<BlogPostType, Optional<BlogPost>> maxLikesPerPostType = posts.stream()
.collect(groupingBy(BlogPost::getType,
maxBy(comparingInt(BlogPost::getLikes))));
Точно так же мы можем применить нисходящий сборщик minBy , чтобы получить сообщение в блоге с минимальным количеством likes .
Обратите внимание, что коллекторы maxBy и minBy учитывают возможность того, что коллекция, к которой она применяется, может быть пустой.
Вот почему тип значения на карте: Optional <BlogPost> .
2.9. Получение сводки по атрибуту сгруппированных результатов
API Collectors предлагает суммирующий коллектор, который можно использовать в тех случаях, когда нам необходимо одновременно рассчитать количество, сумму, минимум, максимум и среднее числового атрибута.
Давайте подсчитаем сводку для атрибута лайков постов блога для каждого типа:
Map<BlogPostType, IntSummaryStatistics> likeStatisticsPerType = posts.stream()
.collect(groupingBy(BlogPost::getType,
summarizingInt(BlogPost::getLikes)));
Объект IntSummaryStatistics для каждого типа содержит значения количества, суммы, среднего, минимального и максимального значений для атрибута likes . Дополнительные итоговые объекты существуют для двойных и длинных значений.
2.10. Отображение сгруппированных результатов в другой тип
Более сложные агрегации могут быть достигнуты путем применения нисходящего коллектора mapping к результатам функции классификации.
Давайте получим объединение _title s сообщений для каждого сообщения в блоге type_ :
Map<BlogPostType, String> postsPerType = posts.stream()
.collect(groupingBy(BlogPost::getType,
mapping(BlogPost::getTitle, joining(", ", "Post titles:[", "]"))));
То, что мы сделали здесь - это сопоставить каждый экземпляр BlogPost с его title , а затем уменьшить поток заголовков сообщений до объединенной String . В этом примере тип значения Map также отличается от типа List по умолчанию.
2.11. Изменение типа возврата Map
При использовании сборщика groupingBy мы не можем делать предположения о типе возвращаемого Map . Если мы хотим конкретно указать, какой тип Map мы хотим получить от группы, то мы можем использовать третий вариант метода groupingBy , который позволяет нам изменять тип Map , передавая функцию поставщика Map .
Давайте возьмем EnumMap , передав функцию поставщика EnumMap методу groupingBy :
EnumMap<BlogPostType, List<BlogPost>> postsPerType = posts.stream()
.collect(groupingBy(BlogPost::getType,
() -> new EnumMap<>(BlogPostType.class), toList()));
3. Параллельная группировка по коллектору
Подобно groupingBy , существует сборщик groupingByConcurrent , который использует многоядерные архитектуры. Этот сборщик имеет три перегруженных метода, которые принимают те же аргументы, что и соответствующие перегруженные методы сборщика groupingBy . Однако тип возвращаемого значения коллектора groupingByConcurrent должен быть экземпляром класса ConcurrentHashMap или его подклассом.
Для одновременного выполнения операции группировки поток должен быть параллельным:
ConcurrentMap<BlogPostType, List<BlogPost>> postsPerType = posts.parallelStream()
.collect(groupingByConcurrent(BlogPost::getType));
Если мы решим передать функцию поставщика Map в сборщик groupingByConcurrent , то нам нужно убедиться, что функция возвращает либо ConcurrentHashMap , либо ее подкласс.
4. Дополнения Java 9
Java 9 принесла два новых сборщика, которые хорошо работают с groupingBy - более подробную информацию об этом можно найти по ссылке:/java9-stream-collectors[здесь].
5. Заключение
В этой статье мы увидели несколько примеров использования сборщика groupingBy , предлагаемого Java 8 Collectors API.
Мы увидели, как groupingBy может использоваться для классификации потока элементов на основе одного из их атрибутов и как дальнейшие результаты классификации могут быть собраны, видоизменены и сведены к конечным контейнерам.
Полная реализация примеров для этой статьи может быть найдена в the проект GitHub .