© Excellent backgrounds/Shutterstock.com
Benutzerdefinierte Kollektoren

Richtig sammeln


Im letzten Beitrag unserer Serie über Lambdas und Streams in Java haben wir die Stream-Operationen reduce() und collect() verglichen. Dabei haben wir collect() zusammen mit dem StringBuilder als Zielcontainer benutzt. Diesmal wollen wir uns ansehen, wie mächtig die Funktionalität von collect() wird, wenn man sie mit einem selbstdefinierten Zielcontainertyp kombiniert.

Die Stream-Operation collect() ist eine terminale Operation, die alle Elemente des Inputstreams in einem Zielcontainer aufsammelt. Wir haben uns das letzte Mal [1] angesehen, dass man diese Variante nutzen kann, um beispielsweise die Elemente eines Stream<String> zu konkatenieren:

R collect(Supplier<R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner)

Als Zielcontainer von collect() wird dabei ein StringBuilder benutzt, der nach dem collect() wieder mit toString() in einen String konvertiert wird. Die Implementierung sieht dann so aus:

String s = IntStream.range(0, 8) //.parallel() .mapToObj(Integer::toString) .collect(() -> new StringBuilder(), (StringBuilder sb1, String s1) -> sb1.append(s1), (StringBuilder sb1, StringBuilder sb2) -> sb1.append(sb2)) .toString(); System.out.println(s);

Der Operation collect() werden dabei drei Lambda-Ausdrücke übergeben: supplier, accumulator und combiner. Der supplier implementiert, wie ein Objekt des Zielcontainers (StringBuilder) erzeugt wird. Der accumulator implementiert, wie ein Element des Streams (String) in den Zielcontainer akkumuliert wird. Und der combiner implementiert, wie zwei Zielcontainer (StringBuilder) zusammen kombiniert werden. Bei einem sequenziellen Stream wird der supplier von collect() dazu benutzt, ein Zielcontainerobjekt zu konstruieren. Danach wird jedes Streamelement mithilfe des accumulators in dem Zielcontainerobjekt aufgesammelt. Der combiner wird nicht gebraucht.

Bei einem parallelen Stream [2] wird die Stream-­Source in der Fork-Phase in Segmente aufgeteilt. Für jedes Segment wird eine Task erzeugt. Diese Tasks werden anschließend in der Execution-Phase parallel ausgeführt. Bei der Ausführung konstruiert jede Task mithilfe des suppliers ein eigenes Zielcontainerobjekt und verwendet den accumulator, um die zur Task gehörenden Streamelemente in diesem Zielcontainerobjekt aufzusammeln. Danach werden in der Join-Phase die Zielcontainerobjekte aller Tasks mit dem combiner zum Gesamtergebnis zusammengeführt.

Bemerkenswert ist dabei die Tatsache, dass der Zielcontainer ein StringBuilder ist, der verändert ...

Exklusives Abo-Special

Angebote für Teams

Für Firmen haben wir individuelle Teamlizenzen. Wir erstellen Ihnen gerne ein passendes Angebot.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang