- Aggregation | Overview
- Aggregation | ExampleData "Person-Klasse"
- Aggregation | for(..:..) Loop
- Aggregation | Sequentiell .stream().forEach
- Aggregation | Sequentielle Streams & Pipes
- Aggregation | Filter & Predicates
- Aggregation | Stream, Predicate, Consumer
- Aggregation | Consumer mit Typ-Parameter
- Aggregation | Predicates über Enums in Collections
- Aggregation | Components einer Pipeline
- Aggregation | Mapping Items auf neuen Typ
- Aggregation | Summary Example 6
- Aggregation | Collect to Map (E07)
- Aggregation | Stream.mapToObj(..)
- Aggregation | Collectors.toMap(a,b,c,d)
- Aggregation | Map.entry(k,v)
- Aggregation | stream().map(*).findFirst().orElse(*)
- Aggregation | Map.orDefault(..)
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.
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.
UIO3 Es ist einfacher als Du denkst.
Stelle noch heute Deine Anfrage.
