Anleitung zur Java 8-Gruppierung nach Collector

Anleitung zur Java 8-Gruppierung von Collector

1. Einführung

In diesem Artikel erfahren Sie anhand verschiedener Beispiele, wie der KollektorgroupingByfunktioniert.

Um das in diesem Artikel behandelte Material zu verstehen, sind Grundkenntnisse der Java 8-Funktionen erforderlich. Sie können sich dieintro to Java 8 Streams und dieguide to Java 8’s Collectors ansehen.

Weitere Lektüre:

Sammeln Sie einen Java-Stream in eine unveränderliche Sammlung

Erfahren Sie, wie Sie Java-Streams in unveränderlichen Sammlungen sammeln.

Read more

Java 8 Collectors toMap

Erfahren Sie, wie Sie die toMap () -Methode der Collectors-Klasse verwenden.

Read more

2. GroupingBy Collectors

Mit der Java 8Stream-API können wir Datensammlungen deklarativ verarbeiten.

Die statischen Factory-MethodenCollectors.groupingBy() undCollectors.groupingByConcurrent() bieten Funktionen, die der Klausel 'GROUP BY' in der SQL-Sprache ähneln. Sie werden zum Gruppieren von Objekten nach bestimmten Eigenschaften und zum Speichern von Ergebnissen in einerMap-Instanz verwendet.

Die überladenen Methoden vongroupingBy:

  • Mit einer Klassifizierungsfunktion als Methodenparameter:

static  Collector>>
  groupingBy(Function classifier)
  • Mit einer Klassifizierungsfunktion und einem zweiten Kollektor als Methodenparameter:

static  Collector>
  groupingBy(Function classifier,
    Collector downstream)
  • Mit einer Klassifizierungsfunktion, einer Lieferantenmethode (die die Implementierung vonMapliefert, 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 eineBlogPost-Klasse (wir verwenden einen Stream vonBlogPost-Objekten):

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

DieBlogPostType:

enum BlogPostType {
    NEWS,
    REVIEW,
    GUIDE
}

DieList vonBlogPost Objekten:

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

Definieren wir auch eineTuple-Klasse, die zum Gruppieren von Posts anhand der Kombination ihrer Attributetype undauthorverwendet wird:

class Tuple {
    BlogPostType type;
    String author;
}

2.2. Einfache Gruppierung nach einer einzelnen Spalte

Beginnen wir mit der einfachstengroupingBy-Methode, die nur eine Klassifizierungsfunktion als Parameter verwendet. Auf jedes Element des Streams wird eine Klassifizierungsfunktion angewendet. Der von der Funktion zurückgegebene Wert wird als Schlüssel für die Zuordnung verwendet, die wir vom KollektorgroupingByerhalten.

So gruppieren Sie die Blog-Beiträge in der Blog-Beitragsliste nachtype:

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

2.3. Gruppieren nach mit einem komplexenMap-Schlüsseltyp

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 Methodenequals undhashcode implementieren.

So gruppieren Sie nach den Blog-Posts in der Liste nachtype undauthor, die in einerTuple-Instanz kombiniert sind:

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

2.4. Ändern des Werttyps des zurückgegebenenMap

Die zweite Überlastung vongroupingBy erfordert einen zusätzlichen zweiten Kollektor (nachgeschalteten Kollektor), der auf die Ergebnisse des ersten Kollektors angewendet wird.

Wenn wir nur eine Klassifizierungsfunktion und keinen nachgeschalteten Kollektor angeben, wird der KollektortoList()hinter den Kulissen verwendet.

Verwenden wir dentoSet()-Kollektor als Downstream-Kollektor und erhalten SieSet von Blog-Posts (anstelle vonList):

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 nach durchzuführen.

So gruppieren Sie dieList vonBlogPosts zuerst nachauthor und dann nachtype:

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

2.6. Abrufen des Durchschnitts aus gruppierten Ergebnissen

Mit dem nachgeschalteten Kollektor können Aggregationsfunktionen in die Ergebnisse der Klassifizierungsfunktion übernommen werden.

So ermitteln Sie die durchschnittliche Anzahl vonlikes für jeden Blog-Beitragtype:

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

2.7. Abrufen der Summe aus gruppierten Ergebnissen

So berechnen Sie die Gesamtsumme vonlikes für jedestype:

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

2.8. Abrufen des Maximums oder Minimums aus gruppierten Ergebnissen

Eine andere Aggregation, die wir durchführen können, ist das Abrufen des Blogposts mit der maximalen Anzahl von Likes:

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

In ähnlicher Weise können wir den Downstream-KollektorminBy anwenden, um den Blog-Beitrag mit der Mindestanzahl vonlikes zu erhalten.

Beachten Sie, dass die KollektorenmaxBy undminBy die Möglichkeit berücksichtigen, dass die Sammlung, auf die sie angewendet wird, leer sein kann. Aus diesem Grund ist der Werttyp in der KarteOptional<BlogPost>.

2.9. Abrufen einer Zusammenfassung für ein Attribut gruppierter Ergebnisse

DieCollectors-API bietet einen zusammenfassenden Kollektor, der in Fällen verwendet werden kann, in denen Anzahl, Summe, Minimum, Maximum und Durchschnitt eines numerischen Attributs gleichzeitig berechnet werden müssen.

Berechnen wir eine Zusammenfassung für das Likes-Attribut der Blog-Beiträge für jeden Typ:

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

DasIntSummaryStatistics-Objekt für jeden Typ enthält die Werte für Anzahl, Summe, Durchschnitt, Min und Max für das Attributlikes. Zusätzliche Zusammenfassungsobjekte sind für doppelte und lange Werte vorhanden.

2.10. Zuordnen gruppierter Ergebnisse zu einem anderen Typ

Komplexere Aggregationen können erreicht werden, indem einmapping nachgeschalteter Kollektor auf die Ergebnisse der Klassifizierungsfunktion angewendet wird.

Lassen Sie uns eine Verkettung dertitles der Beiträge für jeden Blog-Beitragtype erhalten:

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

Was wir hier getan haben, ist, jedeBlogPost-Instanz ihrentitle zuzuordnen und dann den Strom von Post-Titeln auf verketteteString zu reduzieren. In diesem Beispiel unterscheidet sich der Typ des WertsMapebenfalls vom StandardtypList.

2.11. Ändern des ReturnMap-Typs

Bei Verwendung des KollektorsgroupingBy können keine Annahmen über den Typ der zurückgegebenenMap getroffen werden. Wenn wir genau wissen möchten, welchen Typ vonMap wir bis dahin von der Gruppe erhalten möchten, können wir die dritte Variante der MethodegroupingBy verwenden, mit der wir den Typ vonMapändern können ) s durch Übergeben einerMap Lieferantenfunktion.

Lassen Sie uns einEnumMap abrufen, indem wir die Lieferantenfunktion einesEnumMapan diegroupingBy-Methode übergeben:

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

3. Gleichzeitige Gruppierung nach Kollektor

Ähnlich wie beigroupingBy gibt es den KollektorgroupingByConcurrent, der Mehrkernarchitekturen nutzt. Dieser Kollektor verfügt über drei überladene Methoden, die genau dieselben Argumente wie die jeweiligen überladenen Methoden des KollektorsgroupingByverwenden. Der Rückgabetyp des KollektorsgroupingByConcurrent muss jedoch eine Instanz der KlasseConcurrentHashMap oder eine Unterklasse davon sein.

Um eine Gruppierungsoperation gleichzeitig ausführen zu können, muss der Stream parallel sein:

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

Wenn wir die Lieferantenfunktion vonMapan den Kollektor vongroupingByConcurrentübergeben, müssen wir sicherstellen, dass die Funktion entwederConcurrentHashMap oder eine Unterklasse davon zurückgibt.

4. Java 9 Ergänzungen

Java 9 brachte zwei neue Kollektoren mit, die gut mitgroupingBy funktionieren - weitere Informationen hierzu finden Sie unterhere.

5. Fazit

In diesem Artikel haben wir einige Beispiele für die Verwendung desgroupingBy-Kollektors gesehen, der von der Java 8Collectors-API angeboten wird.

Wir haben gesehen, wiegroupingBy verwendet werden können, um einen Strom von Elementen anhand eines ihrer Attribute zu klassifizieren, und wie die Ergebnisse der Klassifizierung weiter gesammelt, mutiert und auf endgültige Container reduziert werden können.

Die vollständige Implementierung der Beispiele für diesen Artikel finden Sie inthe GitHub project.