Streams: Vereinigen

Aus MimiPedia

Hat man zwei Listen von Objekten und möchte diese zusammen in einem Stream verarbeiten, kann man alle Objekte in eine gemeinsame Liste stopfen und diese dann ver-streamen. Das ist selten elegant und oft nicht möglich. Besser wär's, zwei Streams zu erzeugen, diese zusammenzuführen und dann das Ergebnis wie einen einzigen Stream zu behandeln. Das geht ganz einfach mit der – statischem – Methode Streams.concat(). In diesem Beispiel wird ein Stream aus dem Elementen eines List-Objekts und eines Arrays gebildet:

List<String> list = Arrays.asList("a", "b", "c", "d");
String[] array = {"e", "f", "g", "h"};
Stream<String> strom = Stream.concat(list.stream(), Stream.of(array));

Seltsamerweise hat concat() nur zwei Parameter. Um drei Listen zu vereinen, muß man concat() zweimal ineinanderschachteln. Hier fügen wir noch einen dritten Stream hinzu:

List<String> list = Arrays.asList("a", "b", "c", "d");
String[] array = { "e", "f", "g", "h" };
Stream<String> more = Stream.of("i", "j", "k", "l");
Stream<String> strom = Stream.concat(Stream.concat(list.stream(), Stream.of(array)), more);

Man kann diese Einschränkung bei Bedarf mit einer selbstgeschriebenen Methode umgehen.

private <T> Stream<T> concat(Stream<T>... streams) {
    Stream<T> result = Stream.empty();
    for (Stream<T> s : streams) {
         result = Stream.concat(result, s);
    }
    return result;
}

Diese Implementierung erfüllt ihren Zweck, ist aber nicht besonders elegant (nicht zuletzt wegen der häßlichen for-Schleife). Eine sehr viel schönere Lösung wird in der Beschreibung der reduce-Methode gezeigt. concat() arbeitet mit Streams, also müssen zunächst aus List-Objekt und Array je ein Stream generiert werden. Diese werden dann vereint. Gehen wir wieder einen Schritt zurück und betrachten die einzelnen Objekte eines Stroms von Objekten.

Ströme verbreitern: flatMap()

Hat man nur Objekte eines einzigen Typs – nicht wie im concat-Beispiel oben, das List und Array zusammenbrachte – kann man daraus einen Strom formen. Mit der Stream-Methode flatMap() kann man die einzelnen Objekte des Stroms – analog zur Methode map() – verarbeiten. Sie akzeptiert als Eingabe ein Objekt vom Typ des Eingabe-Stroms und liefert als Ergebnis einen Strom von Objekten. Alle dabei entstehenden Ströme werden zu einem einzigen Strom zusammengefaßt. Sehen wir uns das an einem Beispiel an:

List<String> list1 = Arrays.asList("a", "b", "c", "d");
List<String> list2 = Arrays.asList( "e", "f", "g", "h" );
List<String> list3 = Arrays.asList("i", "j", "k", "l");
Stream.of(list1, list2, list3) //
    .flatMap(List::stream) //
    .forEach(System.out::print);
  1. Zunächst werden drei List-Objekte erzeugt (list1, list2 und list3).
  2. Aus diesen wird mit Stream.of() ein Stream-Objekt erzeugt. Der Typ des Streams ist
    Stream<List<String>>
    Es entsteht also ein Strom, der String-Listen als Elemente enthält.
  3. Auf jede Liste im Stream wird nun mit flatMap() die Methode List.stream() angewandt.
    Aus jedem List-Objekt wird nun ein Strom von String-Objekten die zu einem einzigen Strom von Strings vereinigt werden.
  4. Über foreach() wird schließlich jedes Objekt des Stroms ausgegeben. Das Ergebnis lautet "abcdefghijkl"

Die Methode flatMap() ist nicht auf Collections beschränkt. Tatsächlich kann man damit aus jedem Objekt einen Stream machen, wenn man nur die richtige Funktion baut. Hier machen wir aus Strings noch mehr Strings:

Stream.of("123", "456", "789") //
    .map(s -> s.split("")) //
    .flatMap(Stream::of) //
    .forEach(s->System.out.printf("[%s]", s));
  1. Wir beginnen mit einem Stream von drei Strings
  2. Wir zerlegen mit map() und split() jeden String in ein Array von Strings, von dem jedes Element ein Zeichen enthält.
  3. Dann machen wir mit flatMap() aus jedem Array einen Steam von Strings und vereinen die daraus entstehenden Ströme zu einem einzigen Strom.
  4. Schließlich geben wir die einzelnen Zeichen mit umgebenden eckigen Klammern aus.