Java Streams sind seit der Einführung in Java 8 ein mächtiges Werkzeug zur Verarbeitung von Datenströmen. Sie bieten eine klare und deklarative Art, Daten zu verarbeiten, und sind besonders nützlich, um große Datenmengen effizient zu handhaben. Ein wesentlicher Bestandteil dieses Systems sind die sogenannten „Collector“. In diesem Artikel werden wir die verschiedenen Arten von Stream-Collector in Java detailliert untersuchen.
1. Was sind Collectors?
Ein Collector ist ein Interface im Paket java.util.stream
, das vordefinierte Methoden für das Sammeln von Elementen aus einem Stream bereitstellt. Sie bieten die Möglichkeit, Elemente in verschiedene Formen von Ergebnissen zu transformieren, wie Listen, Mengen, Karten oder sogar einzelne Werte.
Das Interface Collector<T, A, R>
definiert drei generische Typen:
- T: Der Typ der Eingabeelemente im Stream.
- A: Der Akkumulatortyp, der die Zwischenzustände während der Sammlung speichert.
- R: Der Ergebnis-Typ, der nach Abschluss der Sammlung zurückgegeben wird.
2. Standard-Collector
Die Standard-Collector sind Teil der Java-Standardbibliothek und decken die häufigsten Anwendungsfälle ab. Hier sind einige der wichtigsten:
2.1 Collectors.toList()
Dieser Collector sammelt die Elemente eines Streams in eine List
.
List<String> names = Stream.of("Alice", "Bob", "Charlie").collect(Collectors.toList());
Code-Sprache: JavaScript (javascript)
2.2 Collectors.toSet()
Dieser Collector sammelt die Elemente eines Streams in eine Set
.
Set<String> uniqueNames = Stream.of("Alice", "Bob", "Alice").collect(Collectors.toSet());
Code-Sprache: JavaScript (javascript)
2.3 Collectors.toMap()
Dieser Collector sammelt die Elemente eines Streams in eine Map
. Es erfordert zwei Funktionen: eine zum Extrahieren des Schlüssels und eine zum Extrahieren des Wertes.
Map<Integer, String> nameMap = Stream.of("Alice", "Bob", "Charlie")
.collect(Collectors.toMap(String::length, Function.identity()));
Code-Sprache: JavaScript (javascript)
2.4 Collectors.joining()
Dieser Collector verbindet die Elemente eines Streams zu einer einzigen String
. Er bietet auch Überladungen, um Trennzeichen, Präfixe und Suffixe anzugeben.
String concatenated = Stream.of("Alice", "Bob", "Charlie").collect(Collectors.joining(", "));
Code-Sprache: JavaScript (javascript)
2.5 Collectors.counting()
Dieser Collector zählt die Elemente eines Streams.
long count = Stream.of("Alice", "Bob", "Charlie").collect(Collectors.counting());
Code-Sprache: JavaScript (javascript)
2.6 Collectors.summingInt()
, Collectors.averagingInt()
Diese Collector summieren bzw. berechnen den Durchschnitt der int-Werte eines Streams.
int totalLength = Stream.of("Alice", "Bob", "Charlie").collect(Collectors.summingInt(String::length));
double averageLength = Stream.of("Alice", "Bob", "Charlie").collect(Collectors.averagingInt(String::length));
Code-Sprache: JavaScript (javascript)
3. Komplexere Collector
Neben den grundlegenden Collectors gibt es auch fortgeschrittenere, die mächtigere Operationen ermöglichen:
3.1 Collectors.groupingBy()
Dieser Collector gruppiert die Elemente eines Streams nach einem Klassifizierungskriterium.
Map<Integer, List<String>> groupedByLength = Stream.of("Alice", "Bob", "Charlie")
.collect(Collectors.groupingBy(String::length));
Code-Sprache: JavaScript (javascript)
3.2 Collectors.partitioningBy()
Dieser Collector partitioniert die Elemente eines Streams nach einem Prädikat in zwei Gruppen: eine, die das Prädikat erfüllt, und eine, die es nicht erfüllt.
Map<Boolean, List<String>> partitionedByLength = Stream.of("Alice", "Bob", "Charlie")
.collect(Collectors.partitioningBy(s -> s.length() > 3));
Code-Sprache: JavaScript (javascript)
3.3 Collectors.reducing()
Dieser Collector reduziert die Elemente eines Streams zu einem einzigen Wert, indem er einen Akkumulator und eine Identität verwendet.
Optional<String> longestName = Stream.of("Alice", "Bob", "Charlie")
.collect(Collectors.reducing((s1, s2) -> s1.length() > s2.length() ? s1 : s2));
Code-Sprache: JavaScript (javascript)
4. Benutzerdefinierte Collector
Manchmal reichen die vordefinierten Collector nicht aus und es ist notwendig, benutzerdefinierte Collector zu erstellen. Dies kann durch die Implementierung des Collector
-Interfaces oder durch die Verwendung der statischen Methode Collector.of()
geschehen.
4.1 Implementierung des Collector
-Interfaces
Ein benutzerdefinierter Collector muss die Methoden supplier()
, accumulator()
, combiner()
, finisher()
und characteristics()
implementieren.
public class ToCustomListCollector<T> implements Collector<T, List<T>, List<T>> {
@Override
public Supplier<List<T>> supplier() {
return ArrayList::new;
}
@Override
public BiConsumer<List<T>, T> accumulator() {
return List::add;
}
@Override
public BinaryOperator<List<T>> combiner() {
return (left, right) -> {
left.addAll(right);
return left;
};
}
@Override
public Function<List<T>, List<T>> finisher() {
return Function.identity();
}
@Override
public Set<Characteristics> characteristics() {
return Collections.unmodifiableSet(EnumSet.of(Characteristics.IDENTITY_FINISH));
}
}
Code-Sprache: PHP (php)
Verwendung:
List<String> customList = Stream.of("Alice", "Bob", "Charlie")
.collect(new ToCustomListCollector<>());
Code-Sprache: JavaScript (javascript)
4.2 Verwendung von Collector.of()
Collector.of()
bietet eine einfachere Möglichkeit, benutzerdefinierte Collector zu erstellen.
Collector<String, List<String>, List<String>> customCollector =
Collector.of(ArrayList::new, List::add, (left, right) -> {
left.addAll(right);
return left;
});
List<String> customList = Stream.of("Alice", "Bob", "Charlie").collect(customCollector);
Code-Sprache: PHP (php)
5. Eigenschaften von Collector
Collector haben verschiedene Eigenschaften, die ihr Verhalten bestimmen:
- UNORDERED: Die Sammlung muss nicht in der Reihenfolge der Stream-Elemente erfolgen.
- CONCURRENT: Der Collector kann mehrere Threads zur gleichen Zeit verwenden.
- IDENTITY_FINISH: Der Akkumulatortyp und der Ergebnis-Typ sind gleich, sodass der Akkumulator direkt als Ergebnis verwendet werden kann.
Diese Eigenschaften können durch das Setzen der characteristics()
-Methode im Collector angegeben werden.
6. Fazit
Stream-Collector in Java bieten eine flexible und leistungsstarke Möglichkeit, Daten zu sammeln und zu transformieren. Von einfachen Sammlungen wie Listen und Mengen bis hin zu komplexeren Gruppierungen und Reduktionen decken die Standard-Collector die meisten Anwendungsfälle ab. Für spezifische Bedürfnisse können benutzerdefinierte Collector implementiert werden, was die Vielseitigkeit und Anpassungsfähigkeit von Java Streams weiter erhöht. Das Verständnis und die effiziente Nutzung dieser Werkzeuge kann die Datenverarbeitung erheblich vereinfachen und optimieren.