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.
Java 8 Collectors toMap
Apprenez à utiliser la méthode toMap () de la classe Collectors.
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 super T,? extends K> classifier)
-
Avec une fonction de classification et un second collecteur comme paramètres de méthode:
static Collector>
groupingBy(Function super T,? extends K> classifier,
Collector super T,A,D> 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 super T,? extends K> classifier,
Supplier mapFactory, Collector super T,A,D> 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.