Streams sind wirklich eine feine Sache. So fein, dass ich heutzutage praktisch keine For-Loops mehr sehe. Streams sind auch tatsächlich einfach und am Ende sauschnell, um die Daten zu verarbeiten.
Aber wie verwende ich Streams, wenn ich zwei For Loops benötigen würde für die Verarbeitung? Also wenn ich eine „Liste in einer anderen Liste“ habe?
Die kurze Antwort ist:
- Wenn man beim Verarbeiten der inneren Liste auf die Objekte der äusseren Liste zugreifen will, verwendet man zwei Streams() mit AnyMatch.
- Wenn man beim Verarbeiten der inneren Liste nicht auf die Objekte der äusseren Liste zugreifen will, kann man FlatMap verwenden.
FlatMap? Was ist das schon wieder für eine Teufelei?
Ja schrecklich, nicht? Da denkst Du, Du hättest endlich mal alles wissenswerte über Java gelernt, und dann fange ich an, von FlatMaps zu schwafeln.
Um FlatMaps zu erklären fange ich gleich an mit dem Beispiel, dass ich auch unten im Code zeigen werden:
Ich habe eine Liste von Müttern und jede Mutter hat wiederum eine Liste von Ihren Kindern. Meine Aufgabe ist es, die Mutter mit dem Kind „Paul“ zu finden.
Die Daten könnten also folgendermassen aussehen:
Äussere Liste:
- Mutter Janet
- Mutter Petra
Janet hat zwei Kinder:
- John
- Jessica
Petra hat zwei Kinder:
- Patrick
- Paul
Wenn ich jetzt eine FlatMap verwende, erstelle ich für die Mütter einen Stream und erhalte dann einfach alle Kinder aneinandergereiht statt „pro Mutter“
Also statt
Janet { John, Jessica } Petra { Patrick, Paul }
erhalte ich
{ John, Jessica, Patrick, Paul }
Wenn ich jetzt die Information der Mutter nicht benötige und nur die Attribute eines Kindes prüfen kann (Beispielsweise: „Ist das Kind älter als 6 Jahre?“) dann funktioniert eine FlatMap sehr gut.
Sobald ich aber die Information der dazugehörigen Mutter brauche, geht das nicht mehr (Beispiel: „Zu welcher Mutter gehört das Kind Paul?“). Dann kann ich zwei Streams verwenden (siehe Codebeispiel unten).
Okay ich bin bereit. Bring mir den Code!
Der Code zeigt zuerst, wie man für Paul auf herkömmliche Weise herausfindet, wer seine Mutter ist. Dann verwende ich zwei Streams mit anyMatch im inneren Stream. Und am Ende noch eine FlatMap, wobei ich aber fälschlicherweise am Ende das Kind erhalte statt die Mutter.
Die Klassen für Mutter und Kind sowie das setzen der Werte finden sich ganz unten in der Klasse.
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class FindPaul {
List<Mother> mothers = new ArrayList<>();
public void findPaul() {
setupMothers();
twoForLoops();
twoStreams();
flatMap();
}
------------------------------------------------------------
private void twoForLoops() {
for(Mother mother: mothers) {
for (Child child: mother.children) {
if("Paul".equals(child.name)) {
System.out.println("1: " + mother.name + " is the mother of Paul");
}
}
}
}
------------------------------------------------------------
private void twoStreams() {
Optional<Mother> paulsMother = mothers.stream().filter(
mother -> mother.children.stream().anyMatch(child -> "Paul".equals(child.name))
).findFirst();
System.out.println("2: " + paulsMother.get().name + " is the mother of Paul");
}
------------------------------------------------------------
// flatMap makes the conversion:
// Stream<List<Object>> -> flatMap -> Stream<Object>
private void flatMap() {
// Fehler: Mit Flatmap hat man keinen Bezug mehr auf das "Parent" Element
// Man kriegt ein "Child", nicht die Mutter
Optional<Child> paulsMother = mothers.stream().flatMap(mother -> mother.children.stream()).filter(child -> "Paul".equals(child.name)).findFirst();
System.out.println("3: " + paulsMother.get().name + " is Paul itself, and who is the mother?");
}
------------------------------------------------------------
private void setupMothers() {
Mother m1 = new Mother("Janet");
m1.children.add(new Child("John"));
m1.children.add(new Child("Jessica"));
Mother m2 = new Mother("Petra");
m2.children.add(new Child("Patrick"));
m2.children.add(new Child("Paul"));
mothers.add(m1);
mothers.add(m2);
}
class Child {
String name;
Child(String name) { this.name = name; }
}
class Mother {
String name;
List<Child> children = new ArrayList<>();
Mother(String name) { this.name = name; }
}
}
Übrigens: Falls jemand eine noch bessere Methode hat, um zwei For Loops zu verarbeiten, bin ich jederzeit bereit, etwas dazuzulernen. 🙂
AnyMatch und AllMatch
Noch ein kurzer Nachtrag: Wenn man zwei For Loops durch Streams ersetzt und dabei die Lösung verwendet, bei der man in der inneren Schleife die äusseren Objekte kennen muss (oben im Code die Methode twoStreams()) dann muss man noch aufpassen, dass man tatsächlich das richtige herausfiltert.
Bei dem angegebenen Beispiel mit „AnyMatch“ muss im inneren Stream einfach ein Kind „Paul“ heissen.
Will man aber, dass alle Kinder „Paul“ heissen, muss man statt AnyMatch „AllMatch“ verwenden. Der Rest des Codes bleibt der gleiche.