Guide de regroupement de Java 8 par collecteur

Guide de Java 8 groupingBy Collector

1. introduction

Dans cet article, nous allons voir comment fonctionne le collecteurgroupingBy à l'aide de divers exemples.

Pour comprendre le contenu de cet article, vous devez avoir une connaissance de base des fonctionnalités de Java 8. Vous pouvez jeter un œil auxintro to Java 8 Streams et auxguide to Java 8’s Collectors.

Lectures complémentaires:

Recueillir un flux Java dans une collection immuable

Apprenez à collecter des flux Java en collections immuables.

Read more

Java 8 Collectors toMap

Apprenez à utiliser la méthode toMap () de la classe Collectors.

Read more

2. GroupingBy Collectors

L'API Java 8Streamnous permet de traiter des collections de données de manière déclarative.

Les méthodes de fabrique statiqueCollectors.groupingBy() etCollectors.groupingByConcurrent() nous fournissent des fonctionnalités similaires à la clause ‘GROUP BY' dans le langage SQL. Ils sont utilisés pour regrouper des objets par propriété et stocker les résultats dans une instanceMap.

Les méthodes surchargées degroupingBy:

  • Avec une fonction de classification comme paramètre de méthode:

static  Collector>>
  groupingBy(Function classifier)
  • Avec une fonction de classification et un second collecteur comme paramètres de méthode:

static  Collector>
  groupingBy(Function classifier,
    Collector downstream)
  • Avec une fonction de classification, une méthode fournisseur (qui fournit l'implémentation deMap qui contiendra le résultat final) et un second collecteur comme paramètres de méthode:

static > Collector
  groupingBy(Function classifier,
    Supplier mapFactory, Collector downstream)

2.1. Exemple de configuration de code

Pour démontrer l’utilisation de groupingBy (), définissons une classeBlogPost (nous utiliserons un flux d’objetsBlogPost):

class BlogPost {
    String title;
    String author;
    BlogPostType type;
    int likes;
}

LesBlogPostType:

enum BlogPostType {
    NEWS,
    REVIEW,
    GUIDE
}

LesList des objetsBlogPost:

List posts = Arrays.asList( ... );

Définissons également une classeTuple qui sera utilisée pour regrouper les publications par la combinaison de leurs attributstype etauthor:

class Tuple {
    BlogPostType type;
    String author;
}

2.2. Regroupement simple par une seule colonne

Commençons par la méthode la plus simplegroupingBy, qui ne prend qu'une fonction de classification comme paramètre. Une fonction de classification est appliquée à chaque élément du flux. La valeur renvoyée par la fonction est utilisée comme clé de la carte que nous obtenons du collecteurgroupingBy.

Pour regrouper les articles de blog dans la liste des articles de blog par leurtype:

Map> postsPerType = posts.stream()
  .collect(groupingBy(BlogPost::getType));

2.3. Regroupement par avec un type de clé complexeMap

La fonction de classification ne se limite pas à renvoyer uniquement une valeur scalaire ou chaîne. La clé de la carte résultante peut être n'importe quel objet tant que nous nous assurons que nous implémentons les méthodesequals ethashcode nécessaires.

Pour regrouper par les articles de blog dans la liste par lestype etauthor combinés dans une instanceTuple:

Map> postsPerTypeAndAuthor = posts.stream()
  .collect(groupingBy(post -> new Tuple(post.getType(), post.getAuthor())));

2.4. Modification du type de valeurMap renvoyé

La deuxième surcharge degroupingBy prend un deuxième collecteur supplémentaire (collecteur aval), qui est appliqué aux résultats du premier collecteur.

Lorsque nous spécifions uniquement une fonction de classification et non un collecteur en aval, le collecteurtoList() est utilisé en arrière-plan.

Utilisons le collecteurtoSet() comme collecteur en aval et obtenons unSet d'articles de blog (au lieu d'unList):

Map> postsPerType = posts.stream()
  .collect(groupingBy(BlogPost::getType, toSet()));

2.5. Regroupement par plusieurs champs

Une application différente du collecteur en aval consiste à effectuer un regroupement secondaire en fonction des résultats du premier groupe.

Pour grouper lesList deBlogPosts d'abord parauthor puis partype:

Map> map = posts.stream()
  .collect(groupingBy(BlogPost::getAuthor, groupingBy(BlogPost::getType)));

2.6. Obtenir la moyenne à partir de résultats groupés

En utilisant le collecteur en aval, nous pouvons appliquer des fonctions d'agrégation aux résultats de la fonction de classification.

Pour trouver le nombre moyen delikes pour chaque article de blogtype:

Map averageLikesPerType = posts.stream()
  .collect(groupingBy(BlogPost::getType, averagingInt(BlogPost::getLikes)));

2.7. Obtenir la somme des résultats groupés

Pour calculer la somme totale delikes pour chaquetype:

Map likesPerType = posts.stream()
  .collect(groupingBy(BlogPost::getType, summingInt(BlogPost::getLikes)));

2.8. Obtention du maximum ou du minimum à partir de résultats groupés

Une autre agrégation que nous pouvons effectuer consiste à obtenir la publication de blog avec le nombre maximal de «j'aime»:

Map> maxLikesPerPostType = posts.stream()
  .collect(groupingBy(BlogPost::getType,
  maxBy(comparingInt(BlogPost::getLikes))));

De même, nous pouvons appliquer le collecteur en avalminBy pour obtenir le billet de blog avec le nombre minimum delikes.

Notez que les collecteursmaxBy etminBy prennent en compte la possibilité que la collection à laquelle il s'applique soit vide. C'est pourquoi le type de valeur dans la carte estOptional<BlogPost>.

2.9. Obtention d'un résumé pour un attribut de résultats groupés

L'APICollectors offre un collecteur de synthèse qui peut être utilisé dans les cas où nous devons calculer le nombre, la somme, le minimum, le maximum et la moyenne d'un attribut numérique en même temps.

Calculons un résumé de l'attribut J'aime des articles de blog pour chaque type différent:

Map likeStatisticsPerType = posts.stream()
  .collect(groupingBy(BlogPost::getType,
  summarizingInt(BlogPost::getLikes)));

L'objetIntSummaryStatistics pour chaque type contient les valeurs count, sum, average, min et max pour l'attributlikes. Des objets récapitulatifs supplémentaires existent pour les valeurs doubles et longues.

2.10. Mappage des résultats groupés vers un type différent

Des agrégations plus complexes peuvent être obtenues en appliquant un collecteur en avalmapping aux résultats de la fonction de classification.

Obtenons une concaténation destitles des articles pour chaque article de blogtype:

Map postsPerType = posts.stream()
  .collect(groupingBy(BlogPost::getType,
  mapping(BlogPost::getTitle, joining(", ", "Post titles: [", "]"))));

Ce que nous avons fait ici est de mapper chaque instance deBlogPost à sestitle, puis de réduire le flux de titres de publication à unString concaténé. Dans cet exemple, le type de la valeurMap est également différent du typeList par défaut.

2.11. Modification du type de retourMap

Lors de l'utilisation du collecteurgroupingBy, nous ne pouvons pas faire d'hypothèses sur le type desMap renvoyés. Si nous voulons être précis sur le type deMap que nous voulons obtenir du groupe, nous pouvons utiliser la troisième variante de la méthodegroupingBy qui nous permet de changer le type deMap) s en passant une fonction fournisseurMap.

Récupérons unEnumMap en passant une fonction fournisseurEnumMap à la méthodegroupingBy:

EnumMap> postsPerType = posts.stream()
  .collect(groupingBy(BlogPost::getType,
  () -> new EnumMap<>(BlogPostType.class), toList()));

3. Regroupement simultané par collecteur

Semblable augroupingBy, il existe le collecteurgroupingByConcurrent, qui exploite les architectures multicœurs. Ce collecteur a trois méthodes surchargées qui prennent exactement les mêmes arguments que les méthodes surchargées respectives du collecteurgroupingBy. Cependant, le type de retour du collecteurgroupingByConcurrent doit être une instance de la classeConcurrentHashMap ou une sous-classe de celle-ci.

Pour effectuer une opération de regroupement simultanément, le flux doit être parallèle:

ConcurrentMap> postsPerType = posts.parallelStream()
  .collect(groupingByConcurrent(BlogPost::getType));

Si nous choisissons de passer une fonction fournisseurMap au collecteurgroupingByConcurrent, nous devons nous assurer que la fonction renvoie soit unConcurrentHashMap soit une sous-classe de celui-ci.

4. Ajouts Java 9

Java 9 a apporté deux nouveaux collecteurs qui fonctionnent bien avecgroupingBy - plus d'informations à ce sujet peuvent être trouvéeshere.

5. Conclusion

Dans cet article, nous avons vu plusieurs exemples d'utilisation du collecteurgroupingBy proposé par l'API Java 8Collectors.

Nous avons vu commentgroupingBy peut être utilisé pour classer un flux d'éléments en fonction de l'un de leurs attributs et comment les résultats de la classification peuvent être collectés, mutés et réduits en conteneurs finaux.

L'implémentation complète des exemples de cet article se trouve dansthe GitHub project.