uio--WebPageMain-Module

JAVA-C2 Collection Advanced Collection Advanced Problemstellung Diagramm

Concurrent Collection Interfaces & Classes Thread-sichere Lists Collection.synchronizedList() CopyOnWriteArrayList (@Bsp?) Thread-sichere Sets Collection.synchronizedSet() CopyOnWriteArraySet Thread-sichere Queues BlockingQueue ArrayBlockingQueue LinkedBlockingQueue PriorityBlockingQueue Nicht-serialisierbare Queues SynchronousQueue DelayQueue Thread-sichere Maps Collection.synchronizedMap(..) ConcurrentHashMap

Aggregate Operations Streams & Aggregate Operations (Overview) @! 15 Page Section Links fehlen! Person ExampleData Aggregate Operations @!section? stream() @!section? for-Loop for-Loop stream() und forEach() Streams und Pipelines Filter und Predicates EXAMPLE 03 Stream/Predicate/Consumer EXAMPLE 04 Consumer mit Generic Type Parameter EXAMPLE 05 Predicates/Enums/Collection "Pipelines" EXAMPLE 06 Mapping to new Type EX6 Summary maptTOInt, ToIntFunction, IntStream.average() OptionalDouble EXAMPLE 07 Map Key-Value-Array zu LinkedHashMap Stream.mapToObj(k,v) collect(Collectors.toMap(a,b,c,d) Map.entry(k,v) map(*).findFirst().orElse(*) Map.getOrDefault(..)

Collection Reduction stream.reduce Collection Collecting Collection stream.collect



@! Kapitel überprüfen!@! Java-Tier von B4 auf B2 verschoben!@! Alle Links/Referenzen checken!

Collection-Parallelism Collection Stream API ab JAVA 8 .parallelStream() .parallelStream() .parallelStream().mapToInt(*).average).getAsDouble() Typ OptionalDouble Begriff Fork Begriff Join Executing Streams in Parallel (01) Concurrent Reduction (02) Ordered Sequentiell (03) Ordered Parallel (04) Side Effects Parallel Computing "Lazyness" (06) Interference (07) State-Full-Lambda Expressions (08) zustandsbehaftete Lambda-Ausdrücke (08)

Collections, Streams & AlgorithmsCollection Streams (Overview) UIO SimulateDownloadDataProvider** connectSimulated():ListINTFilter (prime, schnapps, quer, mod, Operationsfilter, map, sorted, collect, reduce, peek, limit



(Collection FAQ) Datenquellen zu Streams konvertieren Array-Stream Collection-Stream Map-Stream Primitive-Stream Generatoren-Stream Iteratoren-Stream (Collection Implementations) (Collection Algorithms)

Overview

Agregation | Overview

Einführung in die Aggregation / Aggregate Streams

Collections dienen weniger dazu, um irgendwelche Daten zu speichern, als viel mehr, den Zugriff auf Daten zu erlangen, welche in diesen Collections zu finden sind oder über diese gut organisiert werden können. Sogenannte Aggregation Operations dienen dazu, Informationen aus einer Collection zu ermitteln und diese zu einem Ergebnis zusammengefassen, der Aggregation.

Basierend auf der Person-Klasse aus unser ExampleData-Rubrik geben wir einen Einblick in das Prinzip der Aggregation-Operations im Zusammenhang mit der Java Collection Stream API mit deren Hilfe seit Java8/9 alle bisherigen Java Collections aus dem Java Collection Framework erweitert wurden. In diesem Zusammenhang wurden auch eine Vielzahl von Funktionalen Interfaces definiert und/oder konsequent eingesetzt.

Dieses Kapitel setzt dahingehend solide Kenntnisse in Funktionaler Programmierung sowie die Syntax von Lambda-Expressions voraus.

Person-Klasse

Für mehrere Beispiele verwenden wir die nachfolgende Person-Klasse, siehe de/uio3-docs/java/exampledata

Hinweis: Link öffnet neues Browserfenster.

for-Schleife mit List<P>.

Wenn wir Arrays oder Collections als über das generische List<Person> Interfaces betrachten, können wir for (Person p : items) {..} in einer Schleife durchlaufen.

Dieser Ansatz ist einfacher als eine klassische for-Schleife oder der Umweg über einen Iterator.



package com.stuelken.java.b2.aggregation;

import java.util.List;

//@formatter:off
/**
 * Ausgeben aller Personennamen über 
 * eine {@code for(Person p: roster){ .. }} Schleife. 
 * @author t2m
 *
 */
//@formatter:on
public class Example01 {

	public static void main(String[] args) {

		List<Person> roster = Person.createRoster();

		for (Person p : roster) {
			System.out.println(p.getName());
		}

	}
	
}
//@formatter:off
/*
Anton
Berta
Cesar
Dora
Ephigene
Excelsius
Hakaan
Neutrum
Torben
*/
//@formatter:on

List<Person>

List<P> mit stream, forEach

Wenn wir Arrays oder Collections als über das generische List<Person> Interfaces betrachten, können wir .stream() mit .forEach(consumer) verwenden, sofern wir denn wissen, wie was denn ein Consumer ist.

In dieser Version zeigen wir zwei Varianten, und zwar einmal die übliche Kurzfassung im Einzeiler und einmal die vollständige Version im Mehrzeiler, damit man einmal erkennen kann, welche Datentypen diese Methoden von stream() und forEach() eigentlich erwarten.




package com.stuelken.java.b2.aggregation;

import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Stream;


//@formatter:off
/**
* Ausgeben aller Personennamen über 
* eine {@code roster.stream().forEach(e -> System.out.println(e.getName()));} Schleife.
* 
* Erzeugt die Ausgabe der Namen sowie eine Exception:
* stream has already been operated upon or closed
* 
* @author t2m
*/
//@formatter:on
public class Example02 {

	public static void main(String[] args) {

		// Eine Liste aller Personen beschaffen.
		List<Person> mitarbeitende = Person.createRoster();

		// Im Einzeiler
		System.out.println("Einzeiler:");
		mitarbeitende.stream().forEach(e -> System.out.println(e.getName()));

		// Im Mehrzeiler
		System.out.println("\n");
		System.out.println("Mehrzeiler:");

		Stream<Person> s = mitarbeitende.stream();
		Consumer<? super Person> consumer = (streamItem) -> {
			String name = streamItem.getName();
			System.out.println(name);
		};
		s.forEach(consumer);

	// Versuchen, den Stream ein zweites Mal zu verwenden ...
		try {
			long count = s.count();
			System.out.println(count);

		} catch (Exception ex) {
			System.out.println("Fehler:" +ex.getClass().toGenericString());
			System.out.println(ex.getLocalizedMessage().toString());
		}

	}
}

//@formatter:off
/**
Einzeiler:
Anton
Berta
Cesar
Dora
Ephigene
Excelsius
Hakaan
Neutrum
Torben


Mehrzeiler:
Anton
Berta
Cesar
Dora
Ephigene
Excelsius
Hakaan
Neutrum
Torben
Fehler:public class java.lang.IllegalStateException
stream has already been operated upon or closed
* </pre>
* @author t2m
*
*/
//@formatter:on





//@formatter:off
/**
Einzeiler:
Anton
Berta
Cesar
Dora
Ephigene
Excelsius
Hakaan
Neutrum
Torben


Mehrzeiler:
Anton
Berta
Cesar
Dora
Ephigene
Excelsius
Hakaan
Neutrum
Torben
Fehler:public class java.lang.IllegalStateException
stream has already been operated upon or closed
* </pre>
* @author t2m
*
*/
//@formatter:on

Über das generische List-Interface bekommen wir den Zugriff auf die vom Collection Interface vorgegebene Methode stream() welche uns einen Datenstrom von Objekten des Typs <Person> Objekten über den generischen Typparameter verschafft.

Über das generische List-Interface bekommen wir den Zugriff auf die vom Collection Interface vorgegebene Methode stream() welche uns einen Datenstrom von Objekten des Typs <Person> Objekten über den generischen Typparameter verschafft.

Consumer mit Typparameter

Ein Consumer ist ein Funktionales Interfaces für Lambda-Expressions, die genau einen Parameter haben und keinen Rückgabetyp habe, den Wert quasi verbrauchen.

Alle Collection-Interfaces und damit auch alle deren Methoden sind generisch.

Mit Hilfe von Streams kann bedeuten komplexer Problemstellungen lösen.

Pipelines und Streams

Eine Pipeline ist eine Sequenz von Aggregation Operations, dh. im Anschluss an stream() basierenda auf dem Strom an Objekten des generischen Typs nun weitere Methoden wie mitunter Filter-Operationen wie filter(predicate) mit einem Predicate aufrufen.

Auch ein Predicate ist ein Funktionales Interfaces als Typ für Lambda-Expressions.




package com.stuelken.java.b2.aggregation;

import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Stream;

//@formatter:off
/**
* Ausgeben aller Personen in Verbindung mit 
* {@link Collection#stream()}
* und {@link Stream#filter(java.util.function.Predicate)}
* sowie abschließend
* {@link Stream#forEach(Consumer)}.
* 
* 
* @author t2m
*/
//@formatter:on
public class Example03 {

	public static void main(String[] args) {

		// Eine Liste aller Personen beschaffen.
		List<Person> mitarbeitende = Person.createRoster();

		/*
		 * Männer
		 */

		System.out.println("\nAlle maennlichen Mitarbeiter:");
		//@formatter:off
		mitarbeitende
	 .stream()
	 .filter(e -> e.getGender() == Person.Sex.MALE)
	 .forEach(e -> System.out.println(e.getName()));
		//@formatter:on

		/*
		 * Frauen
		 */

		//@formatter:off		
		System.out.println("\nAlle weiblichen Mitarbeiterinnen:");
		mitarbeitende
	 .stream()
	 .filter(e -> e.getGender() == Person.Sex.FEMALE)
	 .forEach(e -> System.out.println(e.getName()));
		//@formatter:on

		/*
		 * Non-binäre Personen
		 */

		//@formatter:off		
		System.out.println("\nAlle anderen:");
		mitarbeitende
	 .stream()
	 .filter(e -> e.getGender() != Person.Sex.FEMALE && e.getGender() != Person.Sex.MALE)
	 .forEach(e -> System.out.println(e.getName()));
		//@formatter:on		
	}
}

//@formatter:off
/*

Alle maennlichen Mitarbeiter:
Anton
Cesar
Dora
Excelsius
Hakaan
Torben

Alle weiblichen Mitarbeiterinnen:
Berta
Ephigene

Alle anderen:
Neutrum

*/
//@formatter:on




Alle maennlichen Mitarbeiter:
Anton
Cesar
Dora
Excelsius
Hakaan
Torben

Alle weiblichen Mitarbeiterinnen:
Berta
Ephigene

Alle anderen:
Neutrum

Das Ergebnis der .stream() Methode einer List<Person> ist ein Stream<Person> Datenstrom.

Ein Stream<Person> Objekt verfügt nun wiederum über eine Stream<T> filter(predicate Methode welche ein Stream<T> filter(Predicate<T>){} predicate erwartet.

Predicate

Ein Predicate ist ein Funktionales Interfaces für Lambda-Expressions welche einen Eingabeparameter haben und als Antwort einen boolean Wert mit true oder false liefern.

Da ein Predicate also immer einen Wahrheitswert liefert, sind Predicates eine Möglichkeit, wie man Prüfbedingungen einem Filter zuweisen und diese Filter dann in einer Pipeline nach und nach anwenden kann.

.filter(predicate).filter(predicate).forEach(..) ist eine typische Pipeline.

java.util.function.Predicate

Stream, Predicate, Consumer

Da Lambda-Expressions den Typ eines funktionalen Interfaces haben, können wir Lambda-Ausdrücke in Variablen speichern und im Prinzip nach Belieben im Programm herumreichen.

Dieser Ansatz ermöglicht es uns, für verschiedene Filter Predicate Ausdrücke zu programmieren die wir anschließend mehrfach verwenden können.




package com.stuelken.java.b2.aggregation;

import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;

//@formatter:off
/**
* Ausgeben aller Personen in Verbindung mit 
* {@link Collection#stream()}
* und {@link Stream#filter(java.util.function.Predicate)}
* sowie abschließend
* {@link Stream#forEach(Consumer)}.
* 
* 
* @author t2m
*/
//@formatter:on
public class Example04 {

	public static void main(String[] args) {
		//@formatter:off

		// Eine Liste aller Personen beschaffen.
		List<Person> mitarbeitende = Person.createRoster();

		/*
		 * Predicate(s) das Alter
		 */
		Predicate<? super Person> predicateM = 
				e -> e.getGender() == Person.Sex.MALE;
				
		Predicate<? super Person> predicateW = 
				e -> e.getGender() == Person.Sex.FEMALE;
				
		Predicate<? super Person> predicateN = 
				e -> e.getGender() == Person.Sex.UNDEFINED;
				

		Predicate<? super Person> predicate_minAge25 = 
				e -> e.getAge() >= 25;
				
		Predicate<? super Person> predicate_maxAge25 =
				e -> e.getAge() <= 25;
				
		Predicate<? super Person> predicate_maxAge50 = 
				e -> e.getAge() <= 50;

		Consumer<? super Person> consumer = // 
				e -> System.out.println(e.getName() + " (" + e.getAge() + ")");

				

		System.out.println("\nMannen >=25 und <= 50:");

		//@formatter:off
		mitarbeitende
	 .stream()
	 .filter(predicateM)
	 .filter(predicate_minAge25)
	 .filter(predicate_maxAge50)
	 .forEach(consumer);
		//@formatter:on

		System.out.println("\nFrauen <=25");

		//@formatter:off
		mitarbeitende
	 .stream()
	 .filter(predicateW)
 .filter(predicate_maxAge25)
	 .forEach(consumer);
		//@formatter:on
	}
}

//@formatter:off
/*

Mannen >=25 und <= 50:
Anton (44)
Cesar (33)

Frauen <=25
Ephigene (10)

*/
//@formatter:on




Alle maennlichen Mitarbeiter:
Anton
Cesar
Dora
Excelsius
Hakaan
Torben

Alle weiblichen Mitarbeiterinnen:
Berta
Ephigene

Alle anderen:
Neutrum

Das Ergebnis der .stream() Methode einer List<Person> ist ein Stream<Person> Datenstrom.

Ein Stream<Person> Objekt verfügt nun wiederum über eine Stream<T> filter(predicate Methode welche ein Stream<T> filter(Predicate<T>){} predicate erwartet.

Consumer mit Typparameter

Ein Consumer ist ein Funktionales Interfaces für Lambda-Expressions, die genau einen Parameter haben und keinen Rückgabetyp habe, den Wert quasi verbrauchen.

Alle Collection-Interfaces und damit auch alle deren Methoden sind generisch.

Mit Hilfe von Streams kann man bedeutend komplexere Problemstellungen lösen.

java.util.function.Predicate ist Teil der Function-Package von Java.

void java.util.stream.Stream.forEach(Consumer<? super Person> action)

LinkedHashMap<PredicateEnum,Predicate<? super Person>>

Da man Lambdas wie normale Werte herumreichen kann, ist es auch möglich, eine generische Collection zu verwenden, in welcher wir wiederum über eine Enumeration statt einen String als Schlüssel Predicate(s) in Massen erfassen und übrigens über diesen Key auch ersetzen können.

Mit LinkedHashMap<PredicateEnum,Predicate<? super Person>> In diesem Beispiel zeigen wir, wie man wiederum den Zugriff auf einen Lambda-Ausdruck in einer Collection über eine Enumerationskonstante adressieren kann. Schöne neue Welt.



package com.stuelken.java.b2.aggregation;

import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
import java.util.LinkedHashMap;

//@formatter:off
/**
* Ausgeben aller Personen in Verbindung mit 
* {@link Collection#stream()}
* und {@link Stream#filter(java.util.function.Predicate)}
* sowie abschließend
* {@link Stream#forEach(Consumer)}.
* 
* 
* @author t2m
*/
//@formatter:on
public class Example05 {

	public static void main(String[] args) {
		//@formatter:off

		// Eine Liste aller Personen beschaffen.
		List<Person> mitarbeitende = Person.createRoster();
		
		
		LinkedHashMap<PredicateEnum,Predicate<? super Person>> filters = new LinkedHashMap<PredicateEnum,Predicate<? super Person>>();
		
		Predicate<? super Person> ueber18 = e -> e.getAge() >=18;
		
		filters.put(PredicateEnum.M, e -> e.getGender() == Person.Sex.MALE);
		filters.put(PredicateEnum.F, e -> e.getGender() == Person.Sex.FEMALE);
		filters.put(PredicateEnum.N, e -> e.getGender() == Person.Sex.UNDEFINED);
		filters.put(PredicateEnum.MIN18, ueber18);
		filters.put(PredicateEnum.MIN25, e -> e.getAge() >= 25);
		filters.put(PredicateEnum.MAX25, e -> e.getAge() <= 25);
		filters.put(PredicateEnum.MIN50, e -> e.getAge() >= 50);
		filters.put(PredicateEnum.MAX50, ueber18);
		filters.put(PredicateEnum.istVolljaehrig, ueber18 );
		
		Consumer<? super Person> consumer = // 
				e -> System.out.println(e.getName() + " (" + e.getAge() + ")");


	 System.out.println("\nReife Mannen jenseits der 50.");

		//@formatter:off
		mitarbeitende
	 .stream()
	 .filter(filters.get(PredicateEnum.M))
	 .filter(filters.get(PredicateEnum.MIN50))
	 .forEach(consumer);
		//@formatter:on

		System.out.println("\nMinderjaehrige Frauen");

		//@formatter:off
		mitarbeitende
	 .stream()
	 .filter(filters.get(PredicateEnum.F))
	 .filter(filters.get(PredicateEnum.istVolljaehrig).negate())
	 .forEach(consumer);
		//@formatter:on
	}
}

//@formatter:off
/*

Mannen >=25 und <= 50:
Anton (44)
Cesar (33)

Frauen <=25
Ephigene (10)

*/
//@formatter:on




Mannen >=25 und <= 50:
Anton (44)
Cesar (33)

Frauen <=25
Ephigene (10)

Es handelt sich hierbei um eine ganz normale generische Collection vom Typ LinkedHashMap<K,T> wie wir diese im Themenblock der generische Programmierung erklärt haben.

Ein Stream<Person> Objekt verfügt nun wiederum über eine Stream<T> filter(predicate Methode welche ein Stream<T> filter(Predicate<T>){} predicate erwartet.

Consumer mit Typparameter

java.util.function.Predicate ist Teil der Function-Package von Java.

void java.util.stream.Stream.forEach(Consumer<? super Person> action)

Komponenten einer Pipeline

Eine Pipeline besteht aus den folgenden Komponenten:

Das kann eine Sammlung (Collection), ein Array, eine Generatorfunktion oder ein I/O-Kanal und damit ein Stream sein.

Eine Zwischenoperation wie filter erzeugt einen neuen Stream.

Das ist wichtig zum Verständnis, denn der Original-Stream wird hierbei nicht verändert.

Ein Stream ist eine Abfolge oder Sequenz, engl. Sequence, von Elementen desjenigen Typs, der über den Typparameter der Collection angegeben wurde.

Im Gegensatz zu einer Sammlung speichert er keine Elemente, sondern transportiert Werte von einer Quelle durch eine Pipeline.

In unserem Beispiel wird ein Stream aus der Mitarbeiter-Collection durch Aufruf der Methode stream() erstellt.

Diese liefert einen neuen Stream, der nur Elemente enthält, die eine bestimmte Bedingung erfüllen. Der Function-Typ hierfür ist das Predicate. Ein Predicate hat einen Eingabe-Parameter und liefert als Antwort einen bool'schen Werte wie true oder false.

Die Syntax von e -> e.getGender() == Person.Sex.MALE weist einen Parameter e aus welcher innerhalb des Lambda-Ausdrucks für den Zugriff auf .getGender() verwendet werden kann, so dass nur diejenigen Elemente im Stream verbleiben und damit nicht verworfen werdne, welcher die Bedingung dieses Predicate erfüllen.

Eine Terminaloperation wie .forEach(..) mit einem Consumer als Parameter erzeugt ein Endergebnis.

Dieses Endergebnis ist im Regelfall kein Stream mehr sondern eines der folgenden Ergebnisse:

result = int, double, long oder ein anderer Zahlenwert beispielsweise für eine Summe.

result = Sammlung, Collection, Array. So kann man versuchen, eine Sammlung über eine Collection in Verbindung mit Operationen und einer Terminal-Operation in eine neue Collection umwandeln.

void: Manche Terminal-Operation(s) haben gar keinen Rückgabewert. So bewirkt der Consumer e -> System.out.println(e.getName()) zwar eine Konsolen-Ausgabe, letztendlich aber keinen Rückgabewert.



Mappen von Items auf neuen Typ

Dsa (engl.) «Mapping» oder auch (deutsch-englisch) «Mappen» ist ein Verfahren, mit welchem alle Werte einer Collection des eines Typs in eine Collection eines anderen Typs übersetzt.

Die mapToInt(..) des Streams erwartet nun eine mapper Variable oder direkt eine Lambda-Ausdruck vom Typ MapToInt Das Erg Basierend auf diesem Strom an Alterswerten können wir anschließend average mit getAsDouble() als Terminal-Operation nutzen, um einen double-Wert zu bekommen.



package com.stuelken.java.b2.aggregation;

import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
import java.util.LinkedHashMap;

//@formatter:off
/**
 * Berechnung des Durchschnittsalters aller Personen
 * 
* @author t2m
*/
//@formatter:on

public class Example06 {

	public static void main(String[] args) {
	//@formatter:off

	// Eine Liste aller Personen beschaffen.
	List<Person> mitarbeitende = Person.createRoster();
		
	// Predicate
	Predicate<? super Person> isMale = p -> p.getGender() == Person.Sex.MALE;
	
	// Durchschnittsalter
	double durchschnitt = 
			
 mitarbeitende // java.util.Collection.List<T>
 .stream() // java.util.stream.Stream<T> java.util.Collection.stream
	 .filter(isMale) // java.util.stream.Stream<T> java.util.stream.Stream.filter() 
	 .mapToInt(Person::getAge) // java.util.IntStream mapToInt(ToIntFunction mapper)
 .average() // OptionalDouble java.util.stream.IntStream.average()
	 .getAsDouble(); // double java.util.OptionalDouble.getAsDouble()
	
 System.out.println("Durchschnitt: " + durchschnitt);
 System.out.println();
	
	
	 int summeAlter =
			 
 mitarbeitende
	 .stream() // java.util.Collection.List<T>
	 .filter(isMale) // java.util.stream.Stream<T> java.util.stream.Stream.filter() 	 
	 .mapToInt(Person::getAge) // java.util.IntStream mapToInt(ToIntFunction mapper)
	 .sum(); // int java.util.stream.IntStream.sum;
	 
 long anzahlPersonen = 
 mitarbeitende
 .stream()
 .filter(isMale)
 .count(); // Anzahl der erfassten Personen

 System.out.println("Summe Alter: " + summeAlter);
	 System.out.println("Anzahl Personen: " + anzahlPersonen);
	 System.out.println("Durchschnittsalter: " + (summeAlter / (double) anzahlPersonen));

}

	//@formatter:on

}

//@formatter:off
/*

Durchschnitt: 40.0

Summe Alter: 240
Anzahl Personen: 6
Durchschnittsalter: 40.0


*/
//@formatter:on




Durchschnitt: 40.0

Summe Alter: 240
Anzahl Personen: 6
Durchschnittsalter: 40.0

Das Ergebnis der .stream() Methode einer List<Person> ist ein Stream<Person> Datenstrom.

Zusammenfassung

mapToInt IntStream

mapToInt() erschafft einen Datenstrom nur von int-Werten. Es gibt auch mapToDouble und andere mapping-Varianten.

ToIntFunction mapper

Die Operation erfordert einen speziellen Lambda-Typ, java.util.Function.ToIntFunction mapper, und zwar muss dieser zwingend einen int-Wert liefern, wenn er ein Person-Objekt bekommt. Die Referenz auf die Person.getAge() Methode ist also mit map(Person.getAge) so eine Adressierung.

IntStream.average() und OptionalDouble

Die av The average operation calculates the average value of the elements contained in a stream of type IntStream. It returns an object of type OptionalDouble. If the stream contains no elements, then the average operation returns an empty instance of OptionalDouble, and invoking the method getAsDouble throws a NoSuchElementException. The JDK contains many terminal operations such as average that return one value by combining the contents of a stream. These operations are called reduction operations; see the section Reduction for more information.

Zwischenoperationen wie filter(..)

Zwischenfunktionen, engl. Intermediate Operation, sind Operationen, welche den bisherigen Stream zwar möglicherweise Filtern, letztendlich aber noch immer einen Stream und damit kein Endergebnis liefern.

Die Anzahl von Intermediate Operations kann durchaus etwas höher sein.

Ein Mapping auf einen neuen Typ erstellt weiderum einen Stream, ist also keine Terminal-Operation oder Reduction-Operation und zählt auch zu den Intermediate Operation(s).

Reduction Operation

Das JDK von JAVA kennt mehrere sogenannte Terminal Operation(s) wie beispielsweise die average() oder die sum() Funktion. Sie alle liefern nach der Verarbeitung des Streams ein Endergebnis mit einem einzigen Wert.

Diese Operationen bezeichnet man auch als Reduction Operation.

Mehr hierzu siehe Themenblock Reduction.

Unterschied foreach-Aggregate-Operation und Iterator

Die Aggregate Operation .forEach(..) wirkt auf den ersten Blick wie eine typische Schleife, wie man diese sinngemäß auch von Iteratoren kennt. Es gibt aber zwischen Stream.forEach() und Collection.iterator() grundlegende Unterschiede.

mapToInt IntStream

Aggregatoperationen enthalten keine Methode wie next, um explizit das nächste Element einer Sammlung zu verarbeiten. Bei interner Delegation bestimmt die Anwendung, welche Sammlung iteriert wird, aber die JDK entscheidet, wie die Iteration abläuft. Bei externer Iteration hingegen legt die Anwendung sowohl die Sammlung als auch die Art der Iteration fest. Externe Iteration ist jedoch auf eine sequentielle Verarbeitung beschränkt, während interne Iteration parallel ausgeführt werden kann – durch Aufteilung eines Problems in Teilprobleme, deren gleichzeitige Lösung und anschließende Zusammenführung der Ergebnisse. Mehr dazu im Abschnitt Parallelismus.

Stream-Operationen

Sie verarbeiten Elemente aus einem Stream: Aggregatoperationen arbeiten mit Elementen aus einem Stream, nicht direkt aus einer Sammlung. Daher werden sie auch Stream-Operationen genannt.

mapToInt IntStream

«Aggregation Operationen» unterstützen Verhalten als Parameter: Für die meisten Aggregatoperationen können Lambda-Ausdrücke als Parameter angegeben werden. Man bezeichnet es auf Englisch als «Behavior as Parameter».

Immer dann, wenn also ein Lambda möglich ist, kann man diese Operation vergleichsweise flexibel durch eine andere ersetzen.

Aggregation | Collect to Map(E07)

In diesem Beispiel zeigen wir, wie wir Key-Value-Paare aus einem Array als IntStream über mapToObject(k,v) in einen Stream<Entry<String,String>> Datenstrom mappen, um alle Key-Value-paare als LinkedHashMap erhalten zu könne.

Hinweis: Das Beispiel erläutert zwar die Befehle korrekt, sollte aber für die Auswertung von Kommandozeilen-Parametern nicht verwendet werden, weil es auch Parameter ohne Wert gibt, so dass die Anzahl am Ende nicht stimmt.

Für die Auswertung von Kommandozeilen-Parametern empfehlen wir unsere Argument Interpreter Klasse.



package com.stuelken.java.c2.streams.e07.collecttomap;

import java.util.*;
import java.util.stream.*;

/**
 * @author t2m
 *
 */
public class StreamBasedParser {

	/**
	 * Diese Klasse zeigt, wie man ein Key-Value-Paar-Array über einen Stream
	 * auslesen kann.
	 * 
	 * Hinweis: Dieses Beispiel ist eher akademisch examplarisch als denn eine
	 * wirklich sinnvolle Lösung, denn manche
	 * 
	 * @param args
	 */
	public static void main(String[] arguments) {
		final String[] args = new String[] { "-h", "localhost", "-p", "8080", "--name", "Server42" };

		// 1. Map bauen
		Map<String, String> params = IntStream.range(0, args.length / 2)
		 .mapToObj(i -> Map.entry(args[2 * i], args[2 * i + 1]))
		 .collect(Collectors.toMap(
		 Map.Entry::getKey,
		 Map.Entry::getValue,
		 (a, b) -> b,
		 LinkedHashMap::new));

		// 2. Port herausfiltern wie bei switch/case
		String port = params.entrySet().stream()
		 .filter(e -> e.getKey().equals("-p") || e.getKey().equals("--port"))
		 .map(Map.Entry::getValue)
		 .findFirst()
		 .orElse("80");

		// 3. Name extrahieren
		String name = params.getOrDefault("--name",
		 params.getOrDefault("-n", "Unnamed"));

		System.out.printf("Server startet auf %s mit Namen '%s'%n", port, name);
	}
}

// Server startet auf 8080 mit Namen 'Server42'


// Server startet auf 8080 mit Namen 'Server42'

Erzeugt uns einen Stream von int-Zahlen für die Hälfte aller Werte in args.

Mapped den aktuellen Integer-Wert von 0 bis n auf ein java.util.Entry Element als Key-Value-Paar.

Map.entry(k,v) kann man Factory-Methode bezeichnen.

Der neu erzeugte Stream<Entry> Datenstrom liefert nun Objekte.

Liefert einen einen Collector für die collect-Methode mit 3 Parametern: identity, aggregation, combiner.

Der erste Wert liest der Key, der zweite ist der Value. Der dritte Werte ist ein Funktionaler Ausdruck welche die merge-Function beinhaltet. Und der letzte Parameter ist der Supplier.

Signatur:

<Entry<String, String>, Object, Object, Map<Object, Object>> Collector<Entry<String, String>, ?, Map<Object, Object>> java.util.stream.Collectors.toMap(Function<? super Entry<String, String>, ? extends Object> keyMapper, Function<? super Entry<String, String>, ? extends Object> valueMapper, BinaryOperator<Object> mergeFunction, Supplier<Map<Object, Object>> mapFactory)

Aggregation | Stream.mapToObj(..)

Stream.mapToObj(..) mapped ein Objekt aus dem Datenstrom zu einem neuen Objekt, hier vom Typ Entry. Da ein Entry immer aus Key und Value besteht, muss die Mapping-Operation von irgendwo zwei Werte bekommen, die im eigentlichen Stream aber gar nicht mitgeliefert wurden.

Die Lösung besteht hierbei darin, dass der funktionale Ausdruck im Scope der main-Methode ist und deshalb auf die final String[] args Variable zugreifen kann; das ist aber nur erlaubt, wenn diese final ist.

Aggregation | Collectors.toMap(a,b,c,d)

Mit diesem Collector erzeugen wir abschließend ein LinkedHashMap Element als sortierte Sammlung.

Die Methode erwartet stets 4 Parameter.

Aggregation | Map.entry(k,v)

Mit dieser Factory-Methode können wir direkt ein Map-artiges Objekt als Container für ein Key-Value-Paar erzeugen. Man hätte Alternativ auch direkt mit new mit dem Konstruktor ein Objekt erzeugen können.

Die Methode erwartet stets 4 Parameter.

Nicht Map.entry mit Map.Entry verwechseln: Map.entry erzeugt einen Entry, während Map.Entry die zugehörige Klasse ist.

Aggregation | stream().map(*).findFirst().orElse(*)

Mit Hilfe von .findFirst() wird der Durchlauf des Streams an dem Punkt abgebrochen und mit einem Stream für Strings zurückgegeben, welcher nur noch einen einzigen Wert hat: Den einen String-Wert wo der zuerst der Port-Parameter erkannt wurde.

Wenn findFirst() keinen Wert hat, kann mit .orElse("80") ein Standardwert vergeben werden.

Aggregation | Map.orDefault(..)

Auf der Suche nach einem Wert basierend auf einem Key als Suchkriterium kann es passieren, dass es kein Wertpaar mit diesem Key in der Collection gibt. Mit Hilfe von .getOrDefault("..") lässt sich für den Fall von null ein Standardwert angeben.

UI ORGANIZED.

UIO3 Es ist einfacher als Du denkst.

Stelle noch heute Deine Anfrage.

uio--WebPageFooter-Module