{"id":38893,"date":"2023-12-06T00:18:30","date_gmt":"2023-12-05T23:18:30","guid":{"rendered":"https:\/\/www.happycoders.eu\/?p=38893"},"modified":"2026-06-09T13:44:46","modified_gmt":"2026-06-09T11:44:46","slug":"stream-gatherers","status":"publish","type":"post","link":"https:\/\/www.happycoders.eu\/de\/java\/stream-gatherers\/","title":{"rendered":"Java Stream Gatherers \u2013 Schreibe deine eigenen Stream-Operationen!"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Die Java-Stream-API wurde mit Java 8 im M\u00e4rz 2014 ver\u00f6ffentlicht und hat uns ein grundlegend neues Werkzeug an die Hand gegeben, um Datenstr\u00f6me zu verarbeiten.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Allerdings f\u00fchrt der begrenzte Satz an intermedi\u00e4ren Operationen \u2013 <code>filter<\/code>, <code>map<\/code>, <code>flatMap<\/code>, <code>mapMulti<\/code>, <code>distinct<\/code>, <code>sorted<\/code>, <code>peek<\/code>, <code>limit<\/code>, <code>skip<\/code>, <code>takeWhile<\/code> und <code>dropWhile<\/code> \u2013 dazu, dass komplexere Datentransformationen durch die Stream-API nicht ausgedr\u00fcckt werden k\u00f6nnen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Es fehlen z. B. verbreitete intermedi\u00e4re Operationen wie <code>window<\/code> und <code>fold<\/code> und zahlreiche mehr, wenn man sich die Feature-Requests der Java-Community ansieht.<\/p>\n\n\n\n<figure class=\"wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio\"><div class=\"wp-block-embed__wrapper\">\n<iframe title=\"Stream Gatherers - Schreibe deine eigenen Stream-Operationen!\" width=\"500\" height=\"281\" src=\"https:\/\/www.youtube.com\/embed\/gICyQwlrEsY?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe>\n<\/div><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Anstatt nun all diese Operationen im <code>Stream<\/code>-Interface zu implementieren, entschieden die JDK-Developer, eine API zu entwickeln, die zum einen im JDK selbst genutzt werden kann, um hei\u00df begehrte intermedi\u00e4re Operationen zu implementieren, und die zum anderen von Entwickler:innen dazu eingesetzt werden kann, um eigene Operationen zu entwickeln.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Diese neue API hei\u00dft \u201eStream Gatherers\u201c und wurde in <a href=\"https:\/\/www.happycoders.eu\/de\/java\/java-22-features\/#stream-gatherers-preview-jep-461\">Java 22<\/a> im M\u00e4rz 2024 \u2013 also genau zehn Jahre nach der Einf\u00fchrung der Stream-API \u2013 als Preview-Feature ver\u00f6ffentlicht und in <a href=\"https:\/\/www.happycoders.eu\/de\/java\/java-24-features\/#stream-gatherers-jep-485\">Java 24<\/a> finalisiert.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In diesem Artikel erf\u00e4hrst du,<\/p>\n\n\n\n<ul class=\"wp-block-list hc-checked-list\">\n<li>was ein Gatherer ist,<\/li>\n\n\n\n<li>wie die neue Stream-Gatherers-API funktioniert, <\/li>\n\n\n\n<li>wie du mit ihr beliebige intermedi\u00e4re Stream-Operationen implementierst,<\/li>\n\n\n\n<li>welche Gatherer das JDK-Team bereits implementiert hat und wie du diese Gatherer erzeugst.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Beginnen wir mit einer kurzen Zusammenfassung, wie die Stream-API \u00fcberhaupt funktioniert.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"stufen-der-stream-api\">Stufen der Stream-API<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Java-Streams bestehen aus drei Stufen:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Stream-Quelle \u2013 diese erzeugt einen Stream, z. B. durch <code>IntStream.of()<\/code> oder <code>Collection.stream()<\/code>.<\/li>\n\n\n\n<li>Intermedi\u00e4re Operationen \u2013 diese transformieren die im Stream enthaltenen Elemente, z. B. die <code>Stream<\/code>-Methoden <code>map()<\/code>, <code>filter()<\/code> und <code>limit()<\/code>.<\/li>\n\n\n\n<li>Terminale Operationen \u2013 diese sammeln z. B. durch <code>toList()<\/code> die Elemente in einer Liste, durch <code>collect(Collectors.toMap())<\/code> in einer Map oder z\u00e4hlen die Elemente mit <code>count()<\/code>.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Hier ist ein einfaches Beispiel \u2013 eine Methode, die z\u00e4hlt, wie viele W\u00f6rter einer bestimmten L\u00e4nge in einer Liste von W\u00f6rtern enthalten sind:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">long<\/span> <span class=\"hljs-title\">countLongWords<\/span><span class=\"hljs-params\">(List&lt;String&gt; words, <span class=\"hljs-keyword\">int<\/span> minLength)<\/span> <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> words.stream()                       <span class=\"hljs-comment\">\/\/ \u27f5 Source<\/span>\n      .map(String::length)                    <span class=\"hljs-comment\">\/\/ \u27f5 Intermediate operation<\/span>\n      .filter(length -&gt; length &gt;= minLength)  <span class=\"hljs-comment\">\/\/ \u27f5 Intermediate operation<\/span>\n      .count();                               <span class=\"hljs-comment\">\/\/ \u27f5 Terminal operation<\/span>\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"terminale-operation-stream-collector\">Terminale Operation: Stream Collector <\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Und hier ein Beispiel, das die W\u00f6rter in Gro\u00dfbuchstaben umwandelt und nach L\u00e4nge gruppiert in einer Map speichert:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">public<\/span> Map&lt;Integer, List&lt;String&gt;&gt; groupByLength(List&lt;String&gt; words) {\n  <span class=\"hljs-keyword\">return<\/span> words.stream()                                 <span class=\"hljs-comment\">\/\/ \u27f5 Source<\/span>\n      .map(String::toUpperCase)                         <span class=\"hljs-comment\">\/\/ \u27f5 Intermediate operation<\/span>\n      .collect(Collectors.groupingBy(String::length));  <span class=\"hljs-comment\">\/\/ \u27f5 Terminal operation<\/span>\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">In diesem zweiten Beispiel wird der terminalen Operation <code>collect()<\/code> ein sogenannter \u201eCollector\u201c \u00fcbergeben. Ein Collector ist ein Objekt einer Klasse, die das <code>Collector<\/code>-Interface implementiert und definiert, was am Ende des Streams mit den Elementen des Streams passieren soll. In diesem Fall sollen sie nach L\u00e4nge gruppiert in eine Map gespeichert werden.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"intermediaere-operation-stream-gatherer\">Intermedi\u00e4re Operation: Stream Gatherer <\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Analog dazu definiert die Stream-Gatherers-API die Methode <code>Stream.gather()<\/code> sowie ein <code>Gatherer<\/code>-Interface. Das folgende Code-Beispiel verwendet die intermedi\u00e4re Operation \u201eFixed Window\u201c, welche die W\u00f6rter in Listen zu jeweils drei W\u00f6rtern gruppiert:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">public<\/span> List&lt;List&lt;String&gt;&gt; groupsOfThree(List&lt;String&gt; words) {\n  <span class=\"hljs-keyword\">return<\/span> words.stream()                  <span class=\"hljs-comment\">\/\/ \u27f5 Source<\/span>\n      .gather(Gatherers.windowFixed(<span class=\"hljs-number\">3<\/span>))  <span class=\"hljs-comment\">\/\/ \u27f5 Intermediate operation<\/span>\n      .toList();                         <span class=\"hljs-comment\">\/\/ \u27f5 Terminal operation<\/span>\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Wenn wir diese Methode z. B. wie folgt aufrufen:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\">List&lt;String&gt; words = List.of(<span class=\"hljs-string\">\"the\"<\/span>, <span class=\"hljs-string\">\"be\"<\/span>, <span class=\"hljs-string\">\"two\"<\/span>, <span class=\"hljs-string\">\"of\"<\/span>, <span class=\"hljs-string\">\"and\"<\/span>, <span class=\"hljs-string\">\"a\"<\/span>, <span class=\"hljs-string\">\"in\"<\/span>, <span class=\"hljs-string\">\"that\"<\/span>);\nList&lt;List&lt;String&gt;&gt; groups = groupsOfThree(words);\nSystem.out.println(groups);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Dann ist die Ausgabe:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"Klartext\" data-shcb-language-slug=\"plaintext\"><span><code class=\"hljs language-plaintext\">&#091;&#091;the, be, two], &#091;of, and, a], &#091;in, that]]<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Klartext<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">plaintext<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">(Da die Stream-Quelle eine Anzahl an Elementen geliefert hat, die nicht ohne Rest durch drei teilbar ist, enth\u00e4lt die letzte Gruppe nur zwei W\u00f6rter.)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Welche Gatherer das JDK bereits mitbringt und wie du sie einsetzt, erf\u00e4hrst du im n\u00e4chsten Kapitel. Wie ein Stream-Gatherer im Inneren aufgebaut ist und wie du eigene schreibst, sehen wir uns danach an.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"die-fuenf-im-jdk-enthaltenen-stream-gatherers\">Die f\u00fcnf im JDK enthaltenen Stream Gatherers<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Bevor wir uns ansehen, wie man eigene Gatherer schreibt, lohnt sich ein Blick auf die Gatherer, die das JDK-Team bereits f\u00fcr uns implementiert hat. Denn in der Praxis wirst du eigene Gatherer eher selten brauchen \u2013 die f\u00fcnf vordefinierten Gatherer aus der Klasse <code>java.util.stream.Gatherers<\/code> decken die h\u00e4ufigsten F\u00e4lle ab:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>windowFixed(int size)<\/code> \u2013 zerlegt den Stream in nicht \u00fcberlappende Bl\u00f6cke fester Gr\u00f6\u00dfe<\/li>\n\n\n\n<li><code>windowSliding(int size)<\/code> \u2013 erzeugt \u00fcberlappende Fenster fester Gr\u00f6\u00dfe<\/li>\n\n\n\n<li><code>fold(Supplier initial, BiFunction folder)<\/code> \u2013 reduziert den Stream zu einem einzigen Element, \u00e4hnlich wie <code>reduce<\/code>, aber als intermedi\u00e4re Operation<\/li>\n\n\n\n<li><code>scan(Supplier initial, BiFunction scanner)<\/code> \u2013 wie <code>fold<\/code>, emittiert aber jeden Zwischenstand<\/li>\n\n\n\n<li><code>mapConcurrent(int maxConcurrency, Function mapper)<\/code> \u2013 f\u00fchrt die Mapping-Funktion mit begrenzter Parallelit\u00e4t \u00fcber virtuelle Threads nebenl\u00e4ufig aus<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Schauen wir sie uns der Reihe nach an.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"windowfixed-bloecke-fester-groesse\">windowFixed \u2013 Bl\u00f6cke fester Gr\u00f6\u00dfe<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Den <code>windowFixed<\/code>-Gatherer hast du bereits im Einf\u00fchrungsbeispiel kennengelernt: Er gruppiert die Stream-Elemente in Listen fester Gr\u00f6\u00dfe. Ein typischer Praxisfall ist die Batch-Verarbeitung \u2013 etwa, wenn du viele Datens\u00e4tze nicht einzeln, sondern in Bl\u00f6cken in eine Datenbank schreiben willst:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\">customers.stream()\n    .gather(Gatherers.windowFixed(<span class=\"hljs-number\">100<\/span>))\n    .forEach(database::insertBatch);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Statt mit <code>subList()<\/code> und Index-Arithmetik zu hantieren, \u00fcbernimmt der Gatherer das Aufteilen. Geht die Anzahl der Elemente nicht ohne Rest auf, enth\u00e4lt das letzte Fenster entsprechend weniger Elemente.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"windowsliding-ueberlappende-fenster\">windowSliding \u2013 \u00fcberlappende Fenster<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code>windowSliding<\/code> funktioniert wie <code>windowFixed<\/code>, doch die Fenster \u00fcberlappen sich und verschieben sich um jeweils ein Element:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\">Stream.of(<span class=\"hljs-number\">1<\/span>, <span class=\"hljs-number\">2<\/span>, <span class=\"hljs-number\">3<\/span>, <span class=\"hljs-number\">4<\/span>, <span class=\"hljs-number\">5<\/span>)\n    .gather(Gatherers.windowSliding(<span class=\"hljs-number\">3<\/span>))\n    .forEach(System.out::println);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Ausgabe:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"Klartext\" data-shcb-language-slug=\"plaintext\"><span><code class=\"hljs language-plaintext\">&#091;1, 2, 3]\n&#091;2, 3, 4]\n&#091;3, 4, 5]<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Klartext<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">plaintext<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Der klassische Anwendungsfall ist der gleitende Durchschnitt. Aus einem <code>Stream&lt;Double&gt;<\/code> mit Messwerten \u2013 zum Beispiel st\u00fcndlichen Temperaturen \u2013 berechnest du damit den Mittelwert \u00fcber jeweils drei aufeinanderfolgende Werte, ohne den Stream vorher in einer Liste zu materialisieren:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\">List&lt;Double&gt; rollingAvg = readings\n    .gather(Gatherers.windowSliding(<span class=\"hljs-number\">3<\/span>))\n    .map(window -&gt; window.stream().mapToDouble(Double::doubleValue).average().orElseThrow())\n    .toList();<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Bei N Werten und Fenstergr\u00f6\u00dfe K erh\u00e4ltst du so N \u2212 K + 1 Fenster. Ein Sonderfall: Ist der Stream k\u00fcrzer als die Fenstergr\u00f6\u00dfe, liefert <code>windowSliding<\/code> ein einziges Fenster mit allen vorhandenen Elementen. Bei einem vollst\u00e4ndig leeren Stream entsteht gar kein Fenster.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"fold-wie-reduce-aber-als-gatherer\">fold \u2013 wie reduce, aber als Gatherer<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code>fold<\/code> reduziert \u2013 wie das aus Java 8 bekannte <code>reduce<\/code> \u2013 den gesamten Stream zu einem einzigen Ergebnis. Der Unterschied: <code>fold<\/code> ist eine intermedi\u00e4re Operation und liefert das Ergebnis als ein-elementigen Stream, nicht als fertigen Wert:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\">List&lt;Integer&gt; result = Stream.of(<span class=\"hljs-number\">10<\/span>, <span class=\"hljs-number\">20<\/span>, <span class=\"hljs-number\">30<\/span>, <span class=\"hljs-number\">40<\/span>)\n    .gather(Gatherers.fold(() -&gt; <span class=\"hljs-number\">0<\/span>, Integer::sum))\n    .toList();\n<span class=\"hljs-comment\">\/\/ result = &#091;100]<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Wof\u00fcr ist das gut, wenn <code>reduce<\/code> doch dasselbe Ergebnis liefert? F\u00fcr zwei Situationen:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Die Pipeline soll nach der Aggregation weitergehen.<\/strong> Da <code>fold<\/code> einen Stream zur\u00fcckgibt, kannst du direkt weitere Operationen anh\u00e4ngen, wie z. B. <code>map<\/code>.<\/li>\n\n\n\n<li><strong>Die Operation ist nicht assoziativ.<\/strong> Bei <code>reduce<\/code> auf einem parallelen Stream werden zun\u00e4chst die parallelen Teilstreams aggregiert und dann die Ergebnisse der Teilstreams. Bei einer nicht assoziativen Operation liefert das u. U. nicht das gew\u00fcnschte Ergebnis. Der <code>fold<\/code>-Gatherer hingegen arbeitet immer sequenziell \u2013 selbst wenn der Stream parallel ist. Deshalb kannst du bei <code>fold<\/code> auch nicht-assoziative Verkn\u00fcpfungen verwenden, etwa eine String-Konkatenation in fester Reihenfolge. <\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Der Startwert wird \u00fcbrigens als <code>Supplier<\/code> \u00fcbergeben und nicht als fertiger Wert \u2013 so l\u00e4sst sich die Erzeugung eines teuren Startobjekts (etwa einer leeren Liste oder einer <code>HashMap<\/code>) bei Bedarf hinausz\u00f6gern.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"scan-jeden-zwischenstand-emittieren\">scan \u2013 jeden Zwischenstand emittieren<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code>scan<\/code> verwendet dieselbe Signatur wie <code>fold<\/code>, emittiert aber nicht nur das Endergebnis, sondern nach jedem Element den aktuellen Zwischenstand:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\">Stream.of(<span class=\"hljs-number\">10<\/span>, <span class=\"hljs-number\">20<\/span>, <span class=\"hljs-number\">30<\/span>, <span class=\"hljs-number\">40<\/span>)\n    .gather(Gatherers.scan(() -&gt; <span class=\"hljs-number\">0<\/span>, Integer::sum))\n    .forEach(System.out::println);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Ausgabe:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"Klartext\" data-shcb-language-slug=\"plaintext\"><span><code class=\"hljs language-plaintext\">10\n30\n60\n100<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Klartext<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">plaintext<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Jedes Eingabe-Element erzeugt genau ein Ausgabe-Element \u2013 n\u00e4mlich die Summe bis zu dieser Stelle. Das ist immer dann n\u00fctzlich, wenn dich nicht das Endergebnis, sondern der Verlauf interessiert: ein laufender Kontostand, eine kumulierte Summe, der Stand zu jedem Zeitpunkt.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"reduce-fold-und-scan-im-vergleich\">reduce, fold und scan im Vergleich<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Da sich die drei leicht verwechseln lassen, hier die Unterschiede auf einen Blick:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th><\/th><th><code>reduce(0, Integer::sum)<\/code><\/th><th><code>fold(() -&gt; 0, Integer::sum)<\/code><\/th><th><code>scan(() -&gt; 0, Integer::sum)<\/code><\/th><\/tr><\/thead><tbody><tr><td>Typ<\/td><td>terminal<\/td><td>intermedi\u00e4r (Gatherer)<\/td><td>intermedi\u00e4r (Gatherer)<\/td><\/tr><tr><td>Ausgabe f\u00fcr <code>[10, 20, 30, 40]<\/code><\/td><td><code>int<\/code> mit Wert <code>100<\/code><\/td><td><code>Stream&lt;Integer&gt;<\/code> mit <code>[100]<\/code><\/td><td><code>Stream&lt;Integer&gt;<\/code> mit <code>[10, 30, 60, 100]<\/code><\/td><\/tr><tr><td>Weiter verkettbar?<\/td><td>nein \u2013 Pipeline endet<\/td><td>ja<\/td><td>ja<\/td><\/tr><tr><td>Verhalten bei <code>parallel()<\/code><\/td><td>Operation muss assoziativ sein<\/td><td>l\u00e4uft immer sequenziell<\/td><td>l\u00e4uft immer sequenziell<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Faustregel: <code>reduce<\/code> f\u00fcr einfache Einzelwert-Aggregationen wie bisher, <code>fold<\/code>, wenn nach der Aggregation noch Stream-Schritte folgen oder die Operation nicht assoziativ ist, und <code>scan<\/code>, wenn du den Verlauf statt nur des Endergebnisses brauchst.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"mapconcurrent-mapping-mit-begrenzter-parallelitaet\">mapConcurrent \u2013 Mapping mit begrenzter Parallelit\u00e4t<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code>mapConcurrent<\/code> ist der spannendste der f\u00fcnf Gatherer, weil hier die Stream-API auf die <a href=\"https:\/\/www.happycoders.eu\/de\/java\/virtual-threads\/\">virtuellen Threads<\/a> aus Java 21 trifft. Er wendet \u2013 wie <code>map<\/code> \u2013 eine Funktion auf jedes Element an, tut das aber nebenl\u00e4ufig in virtuellen Threads, mit einer von dir festgelegten maximalen Parallelit\u00e4t:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\">List&lt;UserData&gt; users = urls.stream()\n    .gather(Gatherers.mapConcurrent(<span class=\"hljs-number\">50<\/span>, <span class=\"hljs-keyword\">this<\/span>::fetchUser))\n    .toList();<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Der erste Parameter begrenzt die Anzahl gleichzeitiger Aufrufe: Bei <code>50<\/code> laufen h\u00f6chstens 50 <code>fetchUser<\/code>-Aufrufe parallel; der 51. wartet, bis einer der ersten 50 fertig ist. Genau daf\u00fcr sind virtuelle Threads gemacht \u2013 tausende blockierende I\/O-Aufrufe auf einer Handvoll Carrier-Threads.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Damit stellt sich die Frage: Wann nimmst du <code>mapConcurrent<\/code> und wann das altbekannte <code>parallel()<\/code>?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Der entscheidende Unterschied liegt im Thread-Modell. <code>parallel()<\/code> verteilt die Arbeit auf Platform-Threads aus dem <code>ForkJoinPool.commonPool<\/code>, dessen Gr\u00f6\u00dfe sich an der Anzahl der CPU-Kerne orientiert \u2013 ideal f\u00fcr CPU-lastige Aufgaben wie Berechnungen oder Sortieren, aber denkbar schlecht f\u00fcr blockierende I\/O: Schon ein paar Dutzend wartende HTTP-Aufrufe legen den von der gesamten JVM genutzten Pool lahm. <code>mapConcurrent<\/code> startet hingegen pro Element einen virtuellen Thread und ist damit genau auf I\/O-lastige Schritte wie HTTP-, Datenbank- oder Dateizugriffe zugeschnitten.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Hinzu kommen zwei praktische Unterschiede: Die maximale Parallelit\u00e4t legst du bei <code>mapConcurrent<\/code> pro Aufruf \u00fcber den Parameter fest, w\u00e4hrend sie bei <code>parallel()<\/code> global durch die Pool-Gr\u00f6\u00dfe vorgegeben ist. Und <code>mapConcurrent<\/code> beh\u00e4lt die Reihenfolge der Elemente bei \u2013 bei <code>parallel()<\/code> ist die Ausgabe-Reihenfolge nicht garantiert, es sei denn, du erzwingst sie mit <code>forEachOrdered<\/code>, was die Parallelisierung wieder ausbremst.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Kurz: <code>parallel()<\/code> bleibt das Werkzeug f\u00fcr CPU-lastige Arbeit, <code>mapConcurrent<\/code> ist die Antwort der Stream-API auf I\/O-lastige Schritte.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Diese f\u00fcnf Gatherer decken einen Gro\u00dfteil der typischen Anforderungen ab. Doch was, wenn du eine Operation brauchst, die nicht dabei ist? Dann implementierst du sie selbst \u2013 und genau das sehen wir uns jetzt an.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"aufbau-eines-stream-gatherers\">Aufbau eines Stream Gatherers<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Bevor wir uns den Aufbau eines Stream-Gatherers ansehen, ist es wichtig, zwei Eigenschaften von Gatherern zu kennen:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Sie k\u00f6nnen einen Status haben, so dass Elemente unterschiedlich transformiert werden k\u00f6nnen, je nachdem, was vorher passiert ist (wof\u00fcr das relevant ist, zeige ich dir gleich an einem Beispiel).<\/li>\n\n\n\n<li>Sie k\u00f6nnen den Stream vorzeitig terminieren, wie es z. B. <code>limit()<\/code> und <code>takeWhile()<\/code> tun.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Gatherer werden aus bis zu vier Komponenten aufgebaut:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Einem optionalen <strong>\u201e<\/strong>Initializer\u201c, der den eben erw\u00e4hnten Status initialisiert.<\/li>\n\n\n\n<li>Einem \u201eIntegrator\u201c, der jedes Element des Streams verarbeitet (ggf. unter Ber\u00fccksichtigung des aktuellen Status), ggf. den Status aktualisiert, Elemente an die n\u00e4chste Stufe der Stream-Pipeline weitergibt und ggf. den Stream vorzeitig beendet.<\/li>\n\n\n\n<li>Einem optionalen \u201eFinisher\u201c, der nach der Verarbeitung des letzten Elements aufgerufen wird, um ggf. anhand des Status weitere Elemente an die n\u00e4chste Stufe der Stream-Pipeline zu emittieren.<\/li>\n\n\n\n<li>Und einem optionalen <strong>\u201e<\/strong>Combiner\u201c, der bei der parallelen Verarbeitung eines Streams eingesetzt wird, um die Status von parallel ausgef\u00fchrten Transformationen zu kombinieren.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">In den folgenden Abschnitten schauen wir uns die Komponenten nach und nach und mit vielen Beispielen an.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"integrator\">Integrator<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Der Integrator ist die einzige Komponente, die zwingend erforderlich ist. Mit ausschlie\u00dflich einem Integrator k\u00f6nnen wir bereits einen einfachen, statuslosen Gatherer entwickeln.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Im Folgenden zeige ich dir, wie sich die <code>Stream.map()<\/code>-Funktion mit einem Gatherer implementieren l\u00e4sst.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Die gezeigten Interfaces haben zus\u00e4tzliche statische oder Default-Methoden, die f\u00fcr ein grundlegendes Verst\u00e4ndnis nicht wichtig sind. Ich lasse sie daher weg und schreibe anstelle der ausgelassenen Methoden drei Punkte.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>Integrator<\/code> ist ein funktionales Interface mit einer <code>integrate()<\/code>-Methode:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-meta\">@FunctionalInterface<\/span>\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">interface<\/span> <span class=\"hljs-title\">Integrator<\/span>&lt;<span class=\"hljs-title\">A<\/span>, <span class=\"hljs-title\">T<\/span>, <span class=\"hljs-title\">R<\/span>&gt; <\/span>{\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">boolean<\/span> <span class=\"hljs-title\">integrate<\/span><span class=\"hljs-params\">(A state, T element, Downstream&lt;? <span class=\"hljs-keyword\">super<\/span> R&gt; downstream)<\/span><\/span>;\n  . . .\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Die <code>integrate()<\/code>-Methode bekommt drei Parameter \u00fcbergeben:&nbsp;<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>den (optionalen) Zustand vom Typ <code>A<\/code>,<\/li>\n\n\n\n<li>das Element vom Typ <code>T<\/code> \u2013 das ist das Element, das die vorherige Stufe der Stream-Pipeline an diese Stufe schickt,<\/li>\n\n\n\n<li>einen Downstream, \u00fcber den die <code>integrate()<\/code>-Methode ggf. Elemente vom Typ <code>R<\/code> an die n\u00e4chste Stufe der Stream-Pipeline schickt.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><code>Downstream<\/code> ist ein funktionales Interface mit einer <code>push()<\/code>-Methode:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-meta\">@FunctionalInterface<\/span>\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">interface<\/span> <span class=\"hljs-title\">Downstream<\/span>&lt;<span class=\"hljs-title\">T<\/span>&gt; <\/span>{\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">boolean<\/span> <span class=\"hljs-title\">push<\/span><span class=\"hljs-params\">(T element)<\/span><\/span>;\n  . . .\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\"><code>push()<\/code> gibt ein <code>boolean<\/code> zur\u00fcck, das anzeigt, ob der Downstream danach weitere Elemente geschickt haben m\u00f6chte oder nicht. Wenn im Downstream z. B. irgendwo ein <code>limit()<\/code> vorkommt, w\u00fcrde hier <code>false<\/code> zur\u00fcckkommen, wenn das Limit erreicht ist.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Auch die <code>Integrator.integrate()<\/code> gibt ein <code>boolean<\/code> zur\u00fcck. Damit zeigt diese Stufe der Stream-Pipeline der vorherigen Stufe an, ob diese Stufe weitere Elemente verarbeiten m\u00f6chte. Wenn nicht, gibt die <code>integrate()<\/code>-Methode <code>false<\/code> zur\u00fcck und schickt damit sozusagen ein Stop-Signal an die Stream-Quelle.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Einen Integrator, der eine Mapping-Funktion aufruft und das Ergebnis der Mapping-Funktion an den Downstream emittiert (also an die n\u00e4chste Verarbeitungsstufe des Streams weiterleitet), k\u00f6nnen wir als Lambda-Funktion wie folgt schreiben. Da wir hier keinen Zustand ben\u00f6tigen, verwenden wir <code>Void<\/code> als Typ f\u00fcr die <code>state<\/code>-Variable:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\">Function&lt;T, R&gt; mapper = . . .\n\nIntegrator&lt;Void, T, R&gt; integrator = \n    (state, element, downstream) -&gt; {\n      R mappedElement = mapper.apply(element);\n      <span class=\"hljs-keyword\">return<\/span> downstream.push(mappedElement);\n    };<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">In der ersten Zeile des Integrators wird die Mapping-Funktion auf das vom Upstream eingehende Element angewendet. In der zweiten Zeile wird das Ergebnis-Element der Mapping-Funktion an den Downstream emittiert und die Antwort des Downstreams zur\u00fcckgegeben.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Da der oben gezeigte Integrator niemals von sich aus <code>false<\/code> zur\u00fcckliefert, sondern nur dann, wenn dies vom Downstream ausgeht, wird der Integrator als \u201egierig\u201c (englisch: \u201egreedy\u201c) bezeichnet. Um der Stream-Pipeline dies anzuzeigen und ihr damit Optimierungen zu erm\u00f6glichen, sollten wir den Integrator daher einmal mit <code>Integrator.ofGreedy()<\/code> wrappen:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-18\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\">Integrator&lt;Void, T, R&gt; integrator =\n    Integrator.ofGreedy(\n        (state, element, downstream) -&gt; {\n          R mappedElement = mapper.apply(element);\n          <span class=\"hljs-keyword\">return<\/span> downstream.push(mappedElement);\n        });\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Um aus dem Integrator schlie\u00dflich einen Gatherer zu machen, verwenden wir die statische <code>Gatherer.of()<\/code>-Methode:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-19\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\">Gatherer&lt;T, Void, R&gt; gatherer = Gatherer.of(integrator);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-19\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Hier ist ein vollst\u00e4ndiges Beispiel mit einer Methode, die einen Gatherer f\u00fcr eine bestimmte Mapping-Funktion erzeugt und einer Methode, die solch einen Gatherer nutzt, um eine Liste von Strings auf deren L\u00e4ngen zu mappen:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-20\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">public<\/span> &lt;T, R&gt; <span class=\"hljs-function\">Gatherer&lt;T, Void, R&gt; <span class=\"hljs-title\">mapping<\/span><span class=\"hljs-params\">(Function&lt;T, R&gt; mapper)<\/span> <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> Gatherer.of(\n      Integrator.ofGreedy(\n          (state, element, downstream) -&gt; {\n            R mappedElement = mapper.apply(element);\n            <span class=\"hljs-keyword\">return<\/span> downstream.push(mappedElement);\n          }));\n}\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> List&lt;Integer&gt; <span class=\"hljs-title\">toLengths<\/span><span class=\"hljs-params\">(List&lt;String&gt; words)<\/span> <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> words.stream()\n      .gather(mapping(String::length))\n      .toList();\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-20\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Damit ist das grundlegende Konzept eines Gatherers erkl\u00e4rt.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"initializer\">Initializer<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Der Gatherer des vorherigen Abschnitts war statuslos, d. h. die Transformation eines Elements war unabh\u00e4ngig von allem, was zuvor passiert ist.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In diesem Abschnitt zeige ich dir, wie sich die <code>Stream.limit()<\/code>-Funktion mit einem Gatherer implementieren l\u00e4sst. Dazu muss der Gatherer die verarbeiteten Elemente z\u00e4hlen und den Stream nach Erreichen der gew\u00fcnschten Anzahl von Elementen vorzeitig terminieren.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Zum Z\u00e4hlen brauchen wir einen Status, einen Z\u00e4hler. Der Initializer ist vom Typ <code>Supplier<\/code> und liefert den initialen Status. Zum Z\u00e4hlen eignet sich als Status z. B. ein <code>AtomicInteger<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-21\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\">Supplier&lt;AtomicInteger&gt; initializer = AtomicInteger::<span class=\"hljs-keyword\">new<\/span>;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-21\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Den limitierenden Integrator implementieren wir wie folgt:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-22\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">int<\/span> maxSize = . . .\n\nIntegrator&lt;AtomicInteger, T, T&gt; integrator = \n    (state, element, downstream) -&gt; {\n      <span class=\"hljs-keyword\">if<\/span> (state.get() &lt; maxSize) {\n        <span class=\"hljs-keyword\">boolean<\/span> result = downstream.push(element);\n        state.incrementAndGet();\n        <span class=\"hljs-keyword\">return<\/span> result;\n      } <span class=\"hljs-keyword\">else<\/span> {\n        <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">false<\/span>;\n      }\n    };<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-22\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Solange unser Status, der Element-Z\u00e4hler, kleiner ist als <code>maxSize<\/code>, emittieren wir die Stream-Elemente in den Downstream, erh\u00f6hen den Z\u00e4hler um eins und geben das Antwort-Boolean des Downstreams zur\u00fcck. Sobald die gew\u00fcnschte Anzahl Elemente erreicht ist, geben wir <code>false<\/code> zur\u00fcck und zeigen damit an, dass der Stream beendet werden soll.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Wir k\u00f6nnten \u00fcbrigens auch in der if-Anweisung <code>state.getAndIncrement()<\/code> anstelle von <code>state.get()<\/code> schreiben und das <code>state.incrementAndGet()<\/code> weglassen. Doch das w\u00e4re etwas komplizierter zu erkl\u00e4ren gewesen. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Beachte, dass wir diesen Integrator nicht mit <code>Integrator.ofGreedy()<\/code> wrappen, da dieser Integrator auch von sich aus <code>false<\/code> zur\u00fcckliefern kann (also nicht nur, wenn dieser Wert vom Downstream kam).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Um aus dem Initializer und dem Integrator einen Gatherer zu machen, verwenden wir die <code>Gatherer.ofSequential()<\/code>-Methode. Der Name dieser Methode weist darauf hin, dass der zur\u00fcckgelieferte Gatherer nicht parallel arbeiten kann. Das liegt daran, dass er einen Status hat, aber keinen Combiner (dazu sp\u00e4ter mehr).<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-23\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\">Gatherer&lt;T, AtomicInteger, T&gt; gatherer = Gatherer.ofSequential(initializer, integrator);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-23\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Das folgende Listing zeigt eine Methode, die einen limitierenden Gatherer aus den zuvor gezeigten Bausteinen erzeugt \u2013 dieses Mal mit <code>state.getAndIncrement()<\/code>, und eine Methode, die diesen Gatherer nutzt, um die ersten drei W\u00f6rter der Wort-Liste zur\u00fcckzugeben:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-24\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">public<\/span> &lt;T&gt; <span class=\"hljs-function\">Gatherer&lt;T, AtomicInteger, T&gt; <span class=\"hljs-title\">limiting<\/span><span class=\"hljs-params\">(<span class=\"hljs-keyword\">int<\/span> maxSize)<\/span> <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> Gatherer.ofSequential(\n      <span class=\"hljs-comment\">\/\/ Initializer<\/span>\n      AtomicInteger::<span class=\"hljs-keyword\">new<\/span>,\n \n      <span class=\"hljs-comment\">\/\/ Integrator<\/span>\n      (state, element, downstream) -&gt; {\n        <span class=\"hljs-keyword\">if<\/span> (state.getAndIncrement() &lt; maxSize) {\n          <span class=\"hljs-keyword\">return<\/span> downstream.push(element);\n        } <span class=\"hljs-keyword\">else<\/span> {\n          <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">false<\/span>;\n        }\n      });\n}\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> List&lt;String&gt; <span class=\"hljs-title\">firstThreeWords<\/span><span class=\"hljs-params\">(List&lt;String&gt; words)<\/span> <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> words.stream()\n      .gather(limiting(<span class=\"hljs-number\">3<\/span>))\n      .toList();\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-24\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Stream Gatherers sind \u00fcbrigens so m\u00e4chtig, dass nicht nur <code>map()<\/code> und <code>limit()<\/code>, sondern jede existierende intermedi\u00e4re Operation der Stream-API auch als Gatherer implementiert werden kann.<\/p>\n\n\n<div class=\"convertkit-form wp-block-convertkit-form\" style=\"\"><script async data-uid=\"1427197203\" src=\"https:\/\/happycoders.kit.com\/1427197203\/index.js\" data-jetpack-boost=\"ignore\" data-no-defer=\"1\" data-no-optimize=\"1\" nowprocket><\/script><\/div>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"finisher\">Finisher<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Um zu erkl\u00e4ren, wof\u00fcr wir einen Finisher ben\u00f6tigen, zeige ich dir in diesem Abschnitt, wie sich der im Einf\u00fchrungskapitel gezeigte \u201eFixed Window\u201c-Gatherer implementieren l\u00e4sst.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Zur Erinnerung: Dieser soll aus einem Stream von Elementen einen Stream von Listen machen, die jeweils eine bestimmte Anzahl der Elemente enthalten.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Wir beginnen einfach mal mit der Implementierung. An einer Stelle werden wir feststellen, dass wir nicht weiterkommen \u2013 und genau dann werde ich den Finisher einf\u00fchren.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Zun\u00e4chst einmal ben\u00f6tigt unser Gatherer einen Status. Da wir Elemente in Listen gruppieren wollen, liegt es nahe, solch eine Liste als Status zu verwenden. Entsprechend implementieren wir den Initializer:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-25\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\">Supplier&lt;List&lt;T&gt;&gt; initializer = ArrayList::<span class=\"hljs-keyword\">new<\/span>;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-25\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Den Integrator implementieren wir wie folgt:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-26\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\">Integrator&lt;List&lt;T&gt;, T, List&lt;T&gt;&gt; integrator =\n    (state, element, downstream) -&gt; {\n      state.add(element);\n      <span class=\"hljs-keyword\">if<\/span> (state.size() == windowSize) {\n        <span class=\"hljs-keyword\">boolean<\/span> result = downstream.push(List.copyOf(state));\n        state.clear();\n        <span class=\"hljs-keyword\">return<\/span> result;\n      } <span class=\"hljs-keyword\">else<\/span> {\n        <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">true<\/span>;\n      }\n    };<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-26\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Wir h\u00e4ngen das ankommende Element an die Status-Liste an. Sobald die Liste die gew\u00fcnschte Gr\u00f6\u00dfe erreicht hat, senden wir eine Kopie der Liste an den Downstream und leeren die Liste.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Das funktioniert allerdings nur, wenn die Anzahl der Elemente ein Vielfaches der Fenstergr\u00f6\u00dfe ist. Wenn wir beispielsweise acht Elemente haben und eine Fenstergr\u00f6\u00dfe von drei, dann w\u00fcrden f\u00fcr die ersten sechs Elemente zwei Listen zu je drei Elementen an den Downstream emittiert werden. Das siebte und achte Element w\u00fcrden ebenfalls in einer Liste liegen, doch da diese Liste die gew\u00fcnschte Gr\u00f6\u00dfe noch nicht erreicht hat, wird sie vom Integrator nicht in den Downstream emittiert.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Genau hier kommt der Finisher ins Spiel. Der Finisher bekommt als Input den Status nach der Verarbeitung aller Stream-Elemente sowie den Downstream und kann dann in Abh\u00e4ngigkeit vom Status weitere Elemente in den Downstream emittieren.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">F\u00fcr die Fixed-Window-Operation w\u00fcrde der Finisher wie folgt aussehen:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-27\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\">BiConsumer&lt;List&lt;T&gt;, Downstream&lt;List&lt;T&gt;&gt;&gt; finisher =\n    (state, downstream) -&gt; {\n      <span class=\"hljs-keyword\">if<\/span> (!state.isEmpty()) {\n        downstream.push(List.copyOf(state));\n      }\n    };<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-27\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Falls die Liste Elemente enth\u00e4lt, wird sie in den Downstream emittiert.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Initializer, Integrator und Finisher kombinieren wir wie folgt zu einem Gatherer:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-28\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\">Gatherer&lt;T, List&lt;T&gt;, List&lt;T&gt;&gt; gatherer =\n    Gatherer.ofSequential(initializer, integrator, finisher);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-28\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Das folgende Listing zeigt eine Methode, die aus den zuvor gezeigten Komponenten einen Window-Gatherer erzeugt, sowie eine Methode, die diesen Gatherer nutzt:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-29\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">public<\/span> &lt;T&gt; Gatherer&lt;T, List&lt;T&gt;, List&lt;T&gt;&gt; windowing(<span class=\"hljs-keyword\">int<\/span> windowSize) {\n  <span class=\"hljs-keyword\">return<\/span> Gatherer.ofSequential(\n      <span class=\"hljs-comment\">\/\/ Initializer<\/span>\n      ArrayList::<span class=\"hljs-keyword\">new<\/span>,\n\n      <span class=\"hljs-comment\">\/\/ Gatherer<\/span>\n      (state, element, downstream) -&gt; {\n        state.add(element);\n        <span class=\"hljs-keyword\">if<\/span> (state.size() == windowSize) {\n          <span class=\"hljs-keyword\">boolean<\/span> result = downstream.push(List.copyOf(state));\n          state.clear();\n          <span class=\"hljs-keyword\">return<\/span> result;\n        } <span class=\"hljs-keyword\">else<\/span> {\n          <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">true<\/span>;\n        }\n      },\n\n      <span class=\"hljs-comment\">\/\/ Finisher<\/span>\n      (state, downstream) -&gt; {\n        <span class=\"hljs-keyword\">if<\/span> (!state.isEmpty()) {\n          downstream.push(List.copyOf(state));\n        }\n      });\n}\n\n<span class=\"hljs-keyword\">public<\/span> List&lt;List&lt;String&gt;&gt; groupWords(List&lt;String&gt; words, <span class=\"hljs-keyword\">int<\/span> groupSize) {\n  <span class=\"hljs-keyword\">return<\/span> words.stream()\n      .gather(windowing(groupSize))\n      .toList();\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-29\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Der Einfachheit halber habe ich im vorangegangenen Beispiel als Statusobjekt eine <code>ArrayList<\/code> verwendet. Das Erzeugen von Kopien und das Leeren der Liste stellt jedoch einen nicht unerheblichen Overhead dar. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Die folgende L\u00f6sung verwendet als Status ein Wrapper-Objekt, das eine Liste enth\u00e4lt, welche direkt in den Downstream gepusht und dann neu erzeugt wird. Diese Variante ist ca. 20 % schneller:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-30\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">public<\/span> &lt;T&gt; Gatherer&lt;T, ?, List&lt;T&gt;&gt; windowing(<span class=\"hljs-keyword\">int<\/span> windowSize) {\n  <span class=\"hljs-keyword\">return<\/span> Gatherer.ofSequential(\n      <span class=\"hljs-comment\">\/\/ Initializer<\/span>\n      () -&gt; <span class=\"hljs-keyword\">new<\/span> Object() { List&lt;T&gt; list = <span class=\"hljs-keyword\">new<\/span> ArrayList&lt;&gt;(); },\n\n      <span class=\"hljs-comment\">\/\/ Gatherer<\/span>\n      (state, element, downstream) -&gt; {\n        state.list.add(element);\n        <span class=\"hljs-keyword\">if<\/span> (state.list.size() == windowSize) {\n          <span class=\"hljs-keyword\">boolean<\/span> result = downstream.push(state.list);\n          state.list = <span class=\"hljs-keyword\">new<\/span> ArrayList&lt;&gt;();\n          <span class=\"hljs-keyword\">return<\/span> result;\n        } <span class=\"hljs-keyword\">else<\/span> {\n          <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">true<\/span>;\n        }\n      },\n\n      <span class=\"hljs-comment\">\/\/ Finisher<\/span>\n      (state, downstream) -&gt; {\n        <span class=\"hljs-keyword\">if<\/span> (!state.list.isEmpty()) {\n          downstream.push(state.list);\n        }\n      });\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-30\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div class=\"wp-block-uagb-container uagb-block-8ae4ca2e alignfull uagb-is-root-container\">\n<div class=\"uagb-block-b769b372 uagb-infobox__content-wrap  uagb-infobox-icon-left uagb-infobox-left uagb-infobox-stacked-mobile uagb-infobox-image-valign-top \"><div class=\"uagb-ifb-icon-wrap\"><svg xmlns=\"https:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 512 512\"><path d=\"M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z\"><\/path><\/svg><\/div><div class=\"uagb-ifb-content\"><div class=\"uagb-ifb-title-wrap\"><\/div><p class=\"uagb-ifb-desc\">Das Statusobjekt <code>new Object() { List&lt;T> list = new ArrayList&lt;>(); }<\/code> ist \u00fcbrigens ein sogenannter \u201enon-denotable type\u201c \u2013 ein Typ, der zwar existiert (weshalb wir auf <code>state.list<\/code> zugreifen k\u00f6nnen), der aber keinen Namen hat. Wenn wir dieses Objekt einer Variablen zuweisen wollen, funktioniert das nur, wenn wir die Variable mit <code>var<\/code> deklarieren:<br><br><code>var status = new Object() { List&lt;String> list = new ArrayList&lt;>(); };<br>status.list.add(\"element\");<br><\/code><br>W\u00fcrdest du in diesem Code <code>var<\/code> durch <code>Object<\/code> ersetzen, k\u00e4me es zu einem Compilerfehler.<\/p><\/div><\/div>\n<\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Im n\u00e4chsten Abschnitt kommen wir zum letzten Stream-Gatherer-Baustein, dem Combiner.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"combiner\">Combiner<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Um einen statusbehafteten Gatherer parallel auszuf\u00fchren, ben\u00f6tigt er eine Combiner-Funktion. Ein Combiner kombiniert in der Join-Phase der parallelen Stream-Verarbeitung jeweils zwei Status zu einem:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full_800\"><img decoding=\"async\" width=\"800\" height=\"333\" src=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/stream-gatherer-split-and-join-800x333.png\" alt=\"Stream Gatherer parallel ausf\u00fchren: Split- und Join-Phasen\" class=\"wp-image-42577\" srcset=\"https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/stream-gatherer-split-and-join-800x333.png 800w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/stream-gatherer-split-and-join-224x93.png 224w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/stream-gatherer-split-and-join-336x140.png 336w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/stream-gatherer-split-and-join-504x210.png 504w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/stream-gatherer-split-and-join-672x280.png 672w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/stream-gatherer-split-and-join-400x167.png 400w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/stream-gatherer-split-and-join-600x250.png 600w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/stream-gatherer-split-and-join-944x393.png 944w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/stream-gatherer-split-and-join-1200x500.png 1200w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/stream-gatherer-split-and-join-1180x490.png 1180w, https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/stream-gatherer-split-and-join.png 1600w\" sizes=\"(max-width: 800px) 100vw, 800px\" \/><\/figure>\n<\/div>\n\n\n<p class=\"wp-block-paragraph\">In diesem Abschnitt wollen wir einen Gatherer implementieren, der entsprechend eines Comparators das gr\u00f6\u00dfte aller eingehenden Elemente in den Downstream emittiert.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Als Status verwenden wir eine <code>AtomicReference<\/code>, die entweder kein Element oder das aktuell gr\u00f6\u00dfte gefundene Element enth\u00e4lt:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-31\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\">Supplier&lt;AtomicReference&lt;T&gt;&gt; initializer = AtomicReference::<span class=\"hljs-keyword\">new<\/span>;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-31\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Der Integrator speichert das eingehende Element im Status, wenn der Status leer ist oder wenn das eingehende Element gr\u00f6\u00dfer ist als das im Status gespeicherte Element. Da der Integrator immer <code>true<\/code> zur\u00fcckliefert, kennzeichnen wir ihn als \u201egreedy\u201c:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-32\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\">Integrator&lt;AtomicReference&lt;T&gt;, T, T&gt; integrator =\n    Integrator.ofGreedy(\n        (state, element, downstream) -&gt; {\n          T bestElement = state.get();\n          <span class=\"hljs-keyword\">if<\/span> (bestElement == <span class=\"hljs-keyword\">null<\/span> || comparator.compare(element, bestElement) &gt; <span class=\"hljs-number\">0<\/span>) {\n            state.set(element);\n          }\n          <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">true<\/span>;\n        });\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-32\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Der Finisher sendet das Element, sofern der Status eines enth\u00e4lt, an den Downstream:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-33\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\">BiConsumer&lt;AtomicReference&lt;T&gt;, Downstream&lt;T&gt;&gt; finisher =\n    (state, downstream) -&gt; {\n      T bestElement = state.get();\n      <span class=\"hljs-keyword\">if<\/span> (bestElement != <span class=\"hljs-keyword\">null<\/span>) {\n        downstream.push(bestElement);\n      }\n    };\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-33\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Und der Combiner kombiniert zwei Status zu einem: <\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Ist ein Status leer, gibt er den jeweils anderen Status zur\u00fcck.<\/li>\n\n\n\n<li>Enthalten beide Status ein Element, gibt er den Status mit dem gr\u00f6\u00dferen Element zur\u00fcck.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Wenn der Eingangsstream leer ist, wird der Combiner nie aufgerufen, d. h. der Fall, dass beide Status leer sind, kann nicht eintreten.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-34\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\">BinaryOperator&lt;AtomicReference&lt;T&gt;&gt; combiner =\n    (state1, state2) -&gt; {\n      T bestElement1 = state1.get();\n      T bestElement2 = state2.get();\n\n      <span class=\"hljs-keyword\">if<\/span> (bestElement1 == <span class=\"hljs-keyword\">null<\/span>) {\n        <span class=\"hljs-keyword\">return<\/span> state2;\n      } <span class=\"hljs-keyword\">else<\/span> <span class=\"hljs-keyword\">if<\/span> (bestElement2 == <span class=\"hljs-keyword\">null<\/span>) {\n        <span class=\"hljs-keyword\">return<\/span> state1;\n      } <span class=\"hljs-keyword\">else<\/span> <span class=\"hljs-keyword\">if<\/span> (comparator.compare(bestElement1, bestElement2) &gt; <span class=\"hljs-number\">0<\/span>) {\n        <span class=\"hljs-keyword\">return<\/span> state1;\n      } <span class=\"hljs-keyword\">else<\/span> {\n        <span class=\"hljs-keyword\">return<\/span> state2;\n      }\n    };<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-34\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Initializer, Integrator, Combiner und Finisher kombinieren wir wieder mit <code>Gatherer.of()<\/code> zu einem Gatherer:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-35\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\">Gatherer&lt;T, AtomicReference&lt;T&gt;, T&gt; gatherer =\n    Gatherer.of(initializer, integrator, combiner, finisher);\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-35\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Und auch f\u00fcr den Combiner folgt ein Listing mit einer Methode, die einen Maximum-Gatherer aus den zuvor gezeigten Bausteinen erzeugt, sowie einer Methode, die diesen in einem parallelen Stream nutzt, um das l\u00e4ngste Wort einer Liste zu finden:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-36\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">public<\/span> &lt;T&gt; Gatherer&lt;T, AtomicReference&lt;T&gt;, T&gt; maximumBy(Comparator&lt;T&gt; comparator) {\n  <span class=\"hljs-keyword\">return<\/span> Gatherer.of(\n      <span class=\"hljs-comment\">\/\/ Initializer<\/span>\n      AtomicReference::<span class=\"hljs-keyword\">new<\/span>,\n\n      <span class=\"hljs-comment\">\/\/ Integrator<\/span>\n      Integrator.ofGreedy(\n          (state, element, downstream) -&gt; {\n            T bestElement = state.get();\n            <span class=\"hljs-keyword\">if<\/span> (bestElement == <span class=\"hljs-keyword\">null<\/span> || comparator.compare(element, bestElement) &gt; <span class=\"hljs-number\">0<\/span>) {\n              state.set(element);\n            }\n            <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">true<\/span>;\n          }),\n\n      <span class=\"hljs-comment\">\/\/ Combiner<\/span>\n      (state1, state2) -&gt; {\n        T bestElement1 = state1.get();\n        T bestElement2 = state2.get();\n\n        <span class=\"hljs-keyword\">if<\/span> (bestElement1 == <span class=\"hljs-keyword\">null<\/span>) {\n          <span class=\"hljs-keyword\">return<\/span> state2;\n        } <span class=\"hljs-keyword\">else<\/span> <span class=\"hljs-keyword\">if<\/span> (bestElement2 == <span class=\"hljs-keyword\">null<\/span>) {\n          <span class=\"hljs-keyword\">return<\/span> state1;\n        } <span class=\"hljs-keyword\">else<\/span> <span class=\"hljs-keyword\">if<\/span> (comparator.compare(bestElement1, bestElement2) &gt; <span class=\"hljs-number\">0<\/span>) {\n          <span class=\"hljs-keyword\">return<\/span> state1;\n        } <span class=\"hljs-keyword\">else<\/span> {\n          <span class=\"hljs-keyword\">return<\/span> state2;\n        }\n      },\n\n      <span class=\"hljs-comment\">\/\/ Finisher<\/span>\n      (state, downstream) -&gt; {\n        T bestElement = state.get();\n        <span class=\"hljs-keyword\">if<\/span> (bestElement != <span class=\"hljs-keyword\">null<\/span>) {\n          downstream.push(bestElement);\n        }\n      });\n}\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> Optional&lt;String&gt; <span class=\"hljs-title\">getLongest<\/span><span class=\"hljs-params\">(List&lt;String&gt; words)<\/span> <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> words.parallelStream()\n      .gather(maximumBy(Comparator.comparing(String::length)))\n      .findFirst();\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-36\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Damit haben wir alle vier Komponenten eines Stream Gatherers beisammen \u2013 und du kennst jetzt sowohl die f\u00fcnf vom JDK mitgelieferten Gatherer als auch den Weg, dir bei Bedarf einen eigenen zu bauen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Im n\u00e4chsten Kapitel sehen wir uns an, wie du mehrere Gatherer zu einer einzigen Transformationssequenz kombinierst.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"gatherers-kombinieren\">Gatherers kombinieren<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">So wie du beispielsweise mehrere <code>filter()<\/code>- und <code>map()<\/code>-Operationen hintereinander schalten kannst, kannst du auch mehrere Gatherer hintereinander aufrufen, z. B. so:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-37\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\"><span class=\"hljs-keyword\">var<\/span> result = source\n    .gather(a)\n    .gather(b)\n    .gather(c)\n    .collect();<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-37\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Wenn du eine bestimmte Abfolge von Gatherern regelm\u00e4\u00dfig ben\u00f6tigst, kannst du diese \u2013 ganz im Sinne von DRY (don't repeat yourself) \u2013 auch zu einem einzigen Gatherer kombinieren, z. B. so:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-38\" data-shcb-language-name=\"Java\" data-shcb-language-slug=\"java\"><span><code class=\"hljs language-java\">Gatherer abc = a.andThen(b).andThen(c);\n\n<span class=\"hljs-keyword\">var<\/span> result = source\n    .gather(abc)\n    .collect();<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-38\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">Java<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">java<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">So kannst du eine beliebig lange Transformationssequenz redundanzfrei auf verschiedene Streams anwenden.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"limitierungen-von-stream-gatherern\">Limitierungen von Stream Gatherern<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Stream Gatherers sind leistungsstarke Werkzeuge, haben jedoch zwei wesentliche Einschr\u00e4nkungen:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Es gibt sie (genau wie Collectoren) nicht f\u00fcr die primitiven Streams <code>IntStream<\/code>, <code>LongStream<\/code> und <code>DoubleStream<\/code>.<\/li>\n\n\n\n<li>Sie haben (genau wie Collectoren) keinen Zugriff auf die (im <code>Spliterator<\/code>-Interface definierten) Charakteristika des Streams. Das bedeutet, dass sie nicht auf der Grundlage dieser Eigenschaften optimiert werden k\u00f6nnen (z. B. der Tatsache, dass die Gr\u00f6\u00dfe bekannt ist oder dass der Stream nur unterschiedliche Elemente enth\u00e4lt).<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"historie\">Historie<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Stream Gatherers<\/em> wurden in folgenden JDK Enhancement Proposals definiert:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Java 22: <a href=\"https:\/\/openjdk.org\/jeps\/461\" target=\"_blank\" rel=\"noreferrer noopener\">JEP 461: Stream Gatherers (Preview)<\/a><\/li>\n\n\n\n<li>Java 23: <a href=\"https:\/\/openjdk.org\/jeps\/473\" target=\"_blank\" rel=\"noreferrer noopener\">JEP 473: Stream Gatherers (Second Preview)<\/a><\/li>\n\n\n\n<li>Java 24: <a href=\"https:\/\/openjdk.org\/jeps\/485\" target=\"_blank\" rel=\"noreferrer noopener\">JEP 485: Stream Gatherers<\/a><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"fazit\">Fazit<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Mit <em>Stream Gatherers<\/em> kannst du beliebige intermedi\u00e4re Stream-Operationen implementieren \u2013 so, wie du mit Collectoren schon immer beliebige terminale Operationen schreiben konntest. Das erlaubt dir, deutlich aussagekr\u00e4ftigere Stream-Pipelines zu bauen als bisher.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">F\u00fcr die h\u00e4ufigsten F\u00e4lle musst du allerdings gar nicht selbst Hand anlegen: Die Klasse <code>java.util.stream.Gatherers<\/code> liefert f\u00fcnf vordefinierte Gatherer mit. Mit <code>windowFixed<\/code> und <code>windowSliding<\/code> zerlegst du einen Stream in nicht \u00fcberlappende bzw. \u00fcberlappende Fenster, mit <code>fold<\/code> reduzierst du ihn \u2013 wie <code>reduce<\/code>, aber als intermedi\u00e4re Operation \u2013 zu einem einzigen Element, mit <code>scan<\/code> emittierst du jeden Zwischenstand der Akkumulation, und mit <code>mapConcurrent<\/code> verarbeitest du I\/O-lastige Mapping-Schritte nebenl\u00e4ufig \u00fcber virtuelle Threads.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Brauchst du eine Operation, die nicht dabei ist, kombinierst du entweder vorhandene Gatherer \u2013 oder du baust dir aus Initializer, Integrator, Combiner und Finisher deinen eigenen.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Welchen der vordefinierten Gatherer wirst du als Erstes einsetzen? Und welche Funktionalit\u00e4t planst du, selbst als Gatherer zu implementieren? Schreibe mir einen Kommentar!<\/p>\n<aside><p>Wenn dir der Artikel weitergeholfen hat, w\u00fcrde ich mich sehr \u00fcber eine positive Bewertung auf meinem <a href=\"https:\/\/www.provenexpert.com\/de-de\/sven-woltmann-happycoders-eu\/7smk\/\" target=\"_blank\" rel=\"noopener\">ProvenExpert-Profil<\/a> freuen. Dein Feedback hilft mir, meine Inhalte weiter zu verbessern und motiviert mich, neue informative Artikel zu schreiben.<\/p>\r\n                        <p>\ud83d\udc49 <a href=\"https:\/\/www.provenexpert.com\/de-de\/sven-woltmann-happycoders-eu\/7smk\/\" target=\"_blank\" rel=\"noopener\">Bewertung abgeben<\/a><\/p>\r\n                        <p>Du m\u00f6chtest \u00fcber alle neuen Java-Features auf dem Laufenden sein? Dann <a href=\"#\" data-formkit-toggle=\"d8ee997126\">klicke hier<\/a>, um dich f\u00fcr den HappyCoders-Newsletter anzumelden.<\/p>\r\n                        <p>\ud83d\udc49 <a href=\"#\" data-formkit-toggle=\"d8ee997126\">Newsletter-Anmeldung<\/a><\/p><\/aside>","protected":false},"excerpt":{"rendered":"<p>Erfahre, was ein Stream-Gatherer ist, und wie sich damit beliebige intermedi\u00e4re Stream-Operationen implementieren lassen.<\/p>\n","protected":false},"author":1,"featured_media":38896,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_seopress_titles_title":"Java Stream Gatherers \u2013 Deine eigenen Stream-Operationen!","_seopress_titles_desc":"Was sind Stream Gatherers in Java? Erfahre, wie du die f\u00fcnf eingebauten Gatherer nutzt und eigene intermedi\u00e4re Stream-Operationen schreibst.","_seopress_robots_index":"","_seopress_robots_follow":"","_seopress_robots_imageindex":"","_seopress_robots_snippet":"","_seopress_robots_primary_cat":"","_seopress_robots_breadcrumbs":"","_seopress_robots_freeze_modified_date":"","_seopress_robots_custom_modified_date":"","_seopress_robots_canonical":"","_seopress_social_fb_title":"","_seopress_social_fb_desc":"","_seopress_social_fb_img":"","_seopress_social_fb_img_attachment_id":0,"_seopress_social_fb_img_width":0,"_seopress_social_fb_img_height":0,"_seopress_social_twitter_title":"","_seopress_social_twitter_desc":"","_seopress_social_twitter_img":"","_seopress_social_twitter_img_attachment_id":0,"_seopress_social_twitter_img_width":0,"_seopress_social_twitter_img_height":0,"_seopress_redirections_value":"","_seopress_redirections_enabled":"","_seopress_redirections_enabled_regex":"","_seopress_redirections_logged_status":"","_seopress_redirections_param":"","_seopress_redirections_type":0,"_seopress_analysis_target_kw":"stream gatherer,stream gatherers,stream-gatherers","_seopress_news_disabled":"","_seopress_video_disabled":"","_seopress_video":[],"_seopress_pro_schemas_manual":[],"_seopress_pro_rich_snippets_disable_all":"","_seopress_pro_rich_snippets_disable":[],"_seopress_pro_schemas":[],"_uag_custom_page_level_css":"","_wp_convertkit_post_meta":{"form":"-1","landing_page":"","tag":"0","restrict_content":"0"},"_metis_text_type":"standard","_metis_text_length":32106,"_post_count":0,"footnotes":""},"categories":[64],"tags":[165],"class_list":["post-38893","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-java","tag-java-fortgeschritten"],"uagb_featured_image_src":{"full":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/stream-gatherers.jpg",1770,986,false],"thumbnail":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/stream-gatherers.jpg",150,84,false],"medium":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/stream-gatherers.jpg",300,167,false],"medium_large":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/stream-gatherers.jpg",768,428,false],"large":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/stream-gatherers.jpg",1024,570,false],"feature_thumb_224":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/stream-gatherers-224x125.jpg",224,125,true],"feature_thumb_336":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/stream-gatherers-336x187.jpg",336,187,true],"feature_thumb_504":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/stream-gatherers-504x281.jpg",504,281,true],"feature_thumb_672":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/stream-gatherers-672x374.jpg",672,374,true],"half_400":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/stream-gatherers-400x223.jpg",400,223,true],"half_600":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/stream-gatherers-600x334.jpg",600,334,true],"full_800":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/stream-gatherers-800x446.jpg",800,446,true],"full_944":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/stream-gatherers-944x526.jpg",944,526,true],"full_1200":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/stream-gatherers-1200x668.jpg",1200,668,true],"wide_1180":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/stream-gatherers-1180x490.jpg",1180,490,true],"wide_1770":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/stream-gatherers-1770x735.jpg",1770,735,true],"1536x1536":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/stream-gatherers.jpg",1536,856,false],"2048x2048":["https:\/\/www.happycoders.eu\/wp-content\/uploads\/2023\/12\/stream-gatherers.jpg",1770,986,false]},"uagb_author_info":{"display_name":"Sven Woltmann","author_link":"https:\/\/www.happycoders.eu\/de\/author\/sven\/"},"uagb_comment_info":0,"uagb_excerpt":"Erfahre, was ein Stream-Gatherer ist, und wie sich damit beliebige intermedi\u00e4re Stream-Operationen implementieren lassen.","public_identification_id":"1b44fe671e1248f0b8ea2558a49a8d36","private_identification_id":"c688ec73adfc4ee8a2a9135609c1de4f","_links":{"self":[{"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/posts\/38893","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/comments?post=38893"}],"version-history":[{"count":18,"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/posts\/38893\/revisions"}],"predecessor-version":[{"id":56538,"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/posts\/38893\/revisions\/56538"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/media\/38896"}],"wp:attachment":[{"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/media?parent=38893"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/categories?post=38893"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.happycoders.eu\/de\/wp-json\/wp\/v2\/tags?post=38893"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}