1. Einleitung
In diesem Tutorial sehen wir anhand verschiedener Beispiele, wie der groupingBy- Kollektor funktioniert.
Damit wir das in diesem Tutorial behandelte Material verstehen können, benötigen wir Grundkenntnisse der Java 8-Funktionen. Wir können uns das Intro zu Java 8 Streams und die Anleitung zu Java 8 Collectors für diese Grundlagen ansehen.
2. Gruppierung durch Sammler
Mit der Java 8 Stream- API können wir Datensammlungen deklarativ verarbeiten.
Die statischen Factory-Methoden Collectors.groupingBy () und Collectors.groupingByConcurrent () bieten Funktionen, die der Klausel ' GROUP BY' in der SQL-Sprache ähneln . Wir verwenden sie zum Gruppieren von Objekten nach einer Eigenschaft und zum Speichern von Ergebnissen in einer Map- Instanz.
Die überladenen Methoden von groupingBy sind:
Zunächst mit einer Klassifizierungsfunktion als Methodenparameter:
static Collector
> groupingBy(Function classifier)
Zweitens mit einer Klassifizierungsfunktion und einem zweiten Kollektor als Methodenparameter:
static Collector
groupingBy(Function classifier, Collector downstream)
Schließlich mit einer Klassifizierungsfunktion, einer Lieferantenmethode (die die Map- Implementierung bereitstellt , die das Endergebnis enthält) und einem zweiten Kollektor als Methodenparameter:
static
Collector groupingBy(Function classifier, Supplier mapFactory, Collector downstream)
2.1. Beispiel-Code-Setup
Um die Verwendung von groupingBy () zu demonstrieren , definieren wir eine BlogPost- Klasse (wir verwenden einen Stream von BlogPost- Objekten):
class BlogPost { String title; String author; BlogPostType type; int likes; }
Als nächstes der BlogPostType :
enum BlogPostType { NEWS, REVIEW, GUIDE }
Dann die Liste der BlogPost- Objekte:
List posts = Arrays.asList( ... );
Definieren wir auch eine Tupel- Klasse, die zum Gruppieren von Posts nach der Kombination ihrer Typ- und Autorenattribute verwendet wird:
class Tuple { BlogPostType type; String author; }
2.2. Einfache Gruppierung nach einer einzelnen Spalte
Beginnen wir mit der einfachsten groupingBy- Methode, die nur eine Klassifizierungsfunktion als Parameter verwendet. Eine Klassifizierungsfunktion wird auf jedes Element des Streams angewendet. Wir verwenden den von der Funktion zurückgegebenen Wert als Schlüssel für die Map, die wir vom groupingBy- Kollektor erhalten.
So gruppieren Sie die Blog-Beiträge in der Blog-Beitragsliste nach Typ :
Map
postsPerType = posts.stream() .collect(groupingBy(BlogPost::getType));
2.3. groupingBy mit einer komplexen Karte Schlüsselart
Die Klassifizierungsfunktion ist nicht darauf beschränkt, nur einen Skalar- oder String-Wert zurückzugeben. Der Schlüssel der resultierenden Karte kann ein beliebiges Objekt sein, solange wir sicherstellen, dass wir die erforderlichen Gleichheits- und Hashcode- Methoden implementieren .
So gruppieren Sie die Blog-Beiträge in der Liste nach Typ und Autor, die in einer Tupel- Instanz zusammengefasst sind:
Map
postsPerTypeAndAuthor = posts.stream() .collect(groupingBy(post -> new Tuple(post.getType(), post.getAuthor())));
2.4. Ändern des Returned Map Wert Typ
Die zweite Überladung von groupingBy erfordert einen zusätzlichen zweiten Kollektor (Downstream-Kollektor), der auf die Ergebnisse des ersten Kollektors angewendet wird.
Wenn wir eine Klassifizierungsfunktion angeben, jedoch keinen nachgeschalteten Kollektor, wird der Kollektor toList () hinter den Kulissen verwendet.
Lassen Sie uns verwenden , um die Toset () Sammler als Downstream - Kollektor und bekommen Set von Blog - Posts (statt einer Liste ):
Map
postsPerType = posts.stream() .collect(groupingBy(BlogPost::getType, toSet()));
2.5. Gruppierung nach mehreren Feldern
Eine andere Anwendung des nachgeschalteten Kollektors besteht darin, eine sekundäre Gruppierung nach den Ergebnissen der ersten Gruppe durch durchzuführen.
So gruppieren Sie die Liste der BlogPosts zuerst nach Autor und dann nach Typ :
Map
map = posts.stream() .collect(groupingBy(BlogPost::getAuthor, groupingBy(BlogPost::getType)));
2.6. Abrufen des Durchschnitts aus gruppierten Ergebnissen
Mit dem Downstream-Kollektor können wir Aggregationsfunktionen in den Ergebnissen der Klassifizierungsfunktion anwenden.
So ermitteln Sie beispielsweise die durchschnittliche Anzahl von Likes für jeden Blogpost- Typ :
Map averageLikesPerType = posts.stream() .collect(groupingBy(BlogPost::getType, averagingInt(BlogPost::getLikes)));
2.7. Abrufen der Summe aus gruppierten Ergebnissen
So berechnen Sie die Gesamtsumme der Likes für jeden Typ :
Map likesPerType = posts.stream() .collect(groupingBy(BlogPost::getType, summingInt(BlogPost::getLikes)));
2.8. Abrufen des Maximums oder Minimums aus gruppierten Ergebnissen
Another aggregation that we can perform is to get the blog post with the maximum number of likes:
Map
maxLikesPerPostType = posts.stream() .collect(groupingBy(BlogPost::getType, maxBy(comparingInt(BlogPost::getLikes))));
Similarly, we can apply the minBy downstream collector to get the blog post with the minimum number of likes.
Note that the maxBy and minBy collectors take into account the possibility that the collection to which they are applied could be empty. This is why the value type in the map is Optional.
2.9. Getting a Summary for an Attribute of Grouped Results
The Collectors API offers a summarizing collector that we can use in cases when we need to calculate the count, sum, minimum, maximum and average of a numerical attribute at the same time.
Let's calculate a summary for the likes attribute of the blog posts for each different type:
Map likeStatisticsPerType = posts.stream() .collect(groupingBy(BlogPost::getType, summarizingInt(BlogPost::getLikes)));
The IntSummaryStatistics object for each type contains the count, sum, average, min and max values for the likes attribute. Additional summary objects exist for double and long values.
2.10. Mapping Grouped Results to a Different Type
We can achieve more complex aggregations by applying a mapping downstream collector to the results of the classification function.
Let's get a concatenation of the titles of the posts for each blog post type:
Map postsPerType = posts.stream() .collect(groupingBy(BlogPost::getType, mapping(BlogPost::getTitle, joining(", ", "Post titles: [", "]"))));
What we have done here is to map each BlogPost instance to its title and then reduce the stream of post titles to a concatenated String. In this example, the type of the Map value is also different from the default List type.
2.11. Modifying the Return Map Type
When using the groupingBy collector, we cannot make assumptions about the type of the returned Map. If we want to be specific about which type of Map we want to get from the group by, then we can use the third variation of the groupingBy method that allows us to change the type of the Map by passing a Map supplier function.
Let's retrieve an EnumMap by passing an EnumMap supplier function to the groupingBy method:
EnumMap
postsPerType = posts.stream() .collect(groupingBy(BlogPost::getType, () -> new EnumMap(BlogPostType.class), toList()));
3. Concurrent groupingBy Collector
Similar to groupingBy is the groupingByConcurrent collector, which leverages multi-core architectures. This collector has three overloaded methods that take exactly the same arguments as the respective overloaded methods of the groupingBy collector. The return type of the groupingByConcurrent collector, however, must be an instance of the ConcurrentHashMap class or a subclass of it.
To do a grouping operation concurrently, the stream needs to be parallel:
ConcurrentMap
postsPerType = posts.parallelStream() .collect(groupingByConcurrent(BlogPost::getType));
If we choose to pass a Map supplier function to the groupingByConcurrent collector, then we need to make sure that the function returns either a ConcurrentHashMap or a subclass of it.
4. Java 9 Additions
Java 9 führte zwei neue Kollektoren ein, die gut mit groupingBy zusammenarbeiten . Weitere Informationen dazu finden Sie hier.
5. Schlussfolgerung
In diesem Artikel haben wir die Verwendung des groupingBy- Kollektors untersucht, der von der Java 8 Collectors- API angeboten wird.
Wir haben gelernt , wie groupingBy verwendet werden kann , um einen Strom von Elementen auf einer ihrer Attribute, und wie die Ergebnisse dieser Klassifizierung zu klassifizieren kann weiter gesammelt werden, mutiert und Endbehälter reduziert.
Die vollständige Implementierung der Beispiele in diesem Artikel finden Sie im GitHub-Projekt.