Anleitung zu Java 8 groupingBy Collector

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.