Wie verwende ich Java Streams bei zwei For Loops?

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.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit deinem WordPress.com-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit deinem Facebook-Konto. Abmelden /  Ändern )

Verbinde mit %s