Руководство по Java 8 groupingBy Collector

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 .