Einen eigenen Collector erstellen
Implementieren Sie die Collector-Schnittstelle, um eine wiederverwendbare Aggregation für einen domänenspezifischen Anwendungsfall zu erstellen.
Einen eigenen Collector erstellen ist eine kostenlose Java Academy-Lektion auf CoddyKit. Dies ist Lektion 4 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des Java Academy-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der Java Academy-Kurs umfasst insgesamt 4 Lektionen.
Warum einen eigenen Collector erstellen?
Wenn die integrierten Collector nicht zu Ihrer Domäne passen, implementieren Sie die Schnittstelle Collector<T, A, R>, um wiederverwendbare und kombinierbare Aggregationslogik zu erstellen. T=Eingabe, A=veränderlicher Akkumulator, R=Ergebnis.
Die Collector-Schnittstelle
Ein Collector besteht aus fünf Komponenten:
supplier()– erstellt den initialen Akkumulatoraccumulator()– fügt ein Element in den Akkumulator eincombiner()– führt zwei Akkumulatoren zusammen (für parallele Streams)finisher()– wandelt den Akkumulator in das endgültige Ergebnis umcharacteristics()– Hinweise zur Optimierung
Die Factory Collector.of()
Der einfachste Weg, einen eigenen Collector zu erstellen, ist Collector.of(), ohne die Schnittstelle direkt zu implementieren:
import java.util.stream.*;
// Collector that builds a comma-separated String
Collector<String, StringBuilder, String> csvCollector = Collector.of(
StringBuilder::new, // supplier
(sb, s) -> {
if (sb.length() > 0) sb.append(',');
sb.append(s);
}, // accumulator
(a, b) -> {
if (a.length() > 0 && b.length() > 0) a.append(',');
a.append(b); return a;
}, // combiner
StringBuilder::toString // finisher
);
String csv = Stream.of("Alice","Bob","Carol").collect(csvCollector);
System.out.println(csv); // Alice,Bob,CarolEigener Collector: TopN
Erstellen Sie einen Collector, der anhand eines Comparators nur die besten N Elemente behält:
import java.util.*;
import java.util.stream.*;
static <T> Collector<T, ?, List<T>> topN(int n, Comparator<T> comp) {
return Collector.of(
() -> new ArrayList<T>(),
(list, e) -> {
list.add(e);
list.sort(comp.reversed());
if (list.size() > n) list.remove(list.size() - 1);
},
(a, b) -> {
a.addAll(b);
a.sort(comp.reversed());
while (a.size() > n) a.remove(a.size() - 1);
return a;
},
Collector.Characteristics.UNORDERED
);
}TopN-Collector verwenden
Wenden Sie den eigenen TopN-Collector auf einen Stream an:
List<Integer> top3 = Stream.of(5,2,9,1,7,3,8)
.collect(topN(3, Comparator.naturalOrder()));
System.out.println(top3); // [9, 8, 7]Merkmalskennzeichnungen
Drei optionale Merkmale dienen der Optimierung von Streams:
IDENTITY_FINISH– der Finisher ist eine Identitätsfunktion und kann übersprungen werdenUNORDERED– die Reihenfolge des Ergebnisses ist unerheblichCONCURRENT– der Akkumulator kann gleichzeitig aufgerufen werden
Das Setzen des falschen Merkmals, etwa CONCURRENT ohne Thread-Sicherheit, führt zu Fehlern.
Eigener Collector: Laufender Durchschnitt
Halten Sie einen laufenden Durchschnitt ohne einen zweiten Durchlauf aufrecht:
record RunningAvg(long count, double sum) {
RunningAvg add(double v) { return new RunningAvg(count+1, sum+v); }
RunningAvg merge(RunningAvg o) { return new RunningAvg(count+o.count, sum+o.sum); }
double avg() { return count == 0 ? 0 : sum / count; }
}
Collector<Double, RunningAvg[], Double> avgCollector = Collector.of(
() -> new RunningAvg[]{new RunningAvg(0, 0)},
(a, v) -> a[0] = a[0].add(v),
(a, b) -> { a[0] = a[0].merge(b[0]); return a; },
a -> a[0].avg()
);
double avg = Stream.of(10.0, 20.0, 30.0).collect(avgCollector);
System.out.println(avg); // 20.0Collector-Schnittstelle direkt implementieren
Für mehr Kontrolle implementieren Sie Collector<T,A,R> als Klasse:
import java.util.function.*;
import java.util.stream.*;
class StringJoiningCollector implements Collector<String, List<String>, String> {
private final String delimiter;
StringJoiningCollector(String delimiter) { this.delimiter = delimiter; }
public Supplier<List<String>> supplier() { return ArrayList::new; }
public BiConsumer<List<String>, String> accumulator() { return List::add; }
public BinaryOperator<List<String>> combiner() {
return (a,b) -> { a.addAll(b); return a; };
}
public Function<List<String>, String> finisher() {
return list -> String.join(delimiter, list);
}
public Set<Characteristics> characteristics() { return Set.of(); }
}Eigene und integrierte Collector vergleichen
Prüfen Sie vor dem Schreiben eines eigenen Collectors, ob eine Kombination integrierter Collector dasselbe Ergebnis liefert – sie sind bereits getestet und optimiert:
// Instead of custom, compose:
Map<String, IntSummaryStatistics> stats = orders.stream().collect(
Collectors.groupingBy(Order::status,
Collectors.summarizingInt(Order::amount))
);
// This replaces many custom collector implementationsThread-Sicherheit in eigenen Collectors
Wenn das Merkmal CONCURRENT gesetzt ist, wird der Akkumulator gleichzeitig von mehreren Threads aufgerufen. Der Akkumulator muss dann thread-sicher sein, etwa durch die Verwendung von ConcurrentHashMap oder AtomicLong. Ohne CONCURRENT übernimmt der Combiner die Parallelisierung sicher.
Praxisbeispiel: Histogramm-Collector
Ordnen Sie numerische Werte in einer Histogramm-Map Klassen zu:
Collector<Integer, Map<Integer, Long>, Map<Integer, Long>> histogram =
Collector.of(
LinkedHashMap::new,
(map, val) -> map.merge(val / 10 * 10, 1L, Long::sum),
(a, b) -> { b.forEach((k,v) -> a.merge(k,v,Long::sum)); return a; },
Collector.Characteristics.IDENTITY_FINISH
);
Map<Integer,Long> hist = Stream.of(5,15,25,12,8,22,18,3)
.collect(histogram);
hist.forEach((bucket, cnt) -> System.out.println(bucket+"-"+(bucket+9)+": "+cnt));Kurze Überprüfung
Welches Merkmal von Collector.Characteristics zeigt an, dass die Finisher-Funktion die Identitätstransformation ist und übersprungen werden kann?
Rückblick: Eigene Collector
Die wichtigsten Punkte:
- Collector<T,A,R>: T=Eingabe, A=Akkumulator, R=Ergebnis
- Verwenden Sie Collector.of() für eine schnelle Implementierung ohne eigene Klasse
- supplier/accumulator/combiner/finisher/characteristics sind die fünf Bestandteile
- IDENTITY_FINISH, UNORDERED und CONCURRENT sind Optimierungsmerkmale
- Bevorzugen Sie die Kombination integrierter Collector und erstellen Sie nur bei Bedarf eigene
Häufig gestellte Fragen
Ist die Lektion „Einen eigenen Collector erstellen“ kostenlos?
Ja — der vollständige Text von „Einen eigenen Collector erstellen“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des Java Academy-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der Java Academy-Kurs umfasst insgesamt 4 Lektionen.
Was lerne ich in „Einen eigenen Collector erstellen“?
Implementieren Sie die Collector-Schnittstelle, um eine wiederverwendbare Aggregation für einen domänenspezifischen Anwendungsfall zu erstellen. Du übst Java Academy mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.
Brauche ich Erfahrung, um Java Academy zu starten?
Keine Vorkenntnisse erforderlich. Java Academy auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 4 von 4.
Wie lange dauert die Lektion „Einen eigenen Collector erstellen“?
Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.
Kann ich in dieser Java Academy-Lektion Code schreiben und ausführen?
Ja. Jede Java Academy-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.
Alle Lektionen in diesem Kurs
- groupingBy: Elemente klassifizieren
- partitioningBy und counting
- toMap, joining und summarizing
- Einen eigenen Collector erstellen