java 8 (jsr335): lambdas, streams, funktionale interfaces, default-methoden
TRANSCRIPT
Java 8 (JSR 335): Lambdas, Streams, Funktionale Interfaces, Default-MethodenMartin Lehmann und Markus Günther, Accso GmbH
Vortrag auf der Java User Group Frankfurt am 26. März 2014
2
Co
pyr
igh
t©
201
4 A
ccso
Gm
bHAbout me
� 42 Jahre
� Studium der Informatik an der Universität Würzburg
� Berufsstart 1997
� Einstieg bei sd&m 2001
� Verschiedene industrielle Softwareprojekte als Entwickler, Architekt, Projektmanager, Berater
� Mitarbeit bei sd&m Research zu Quasar von 2006-2007
� Seit 2010 Mitglied der Geschäftsleitung und CTO beim IT-DienstleisterAccso – Accelerated Solutions GmbH in Darmstadtwww.accso.de
� Interne Weiterbildung, Praktikum zu Java 8 im Sommer 2013 auf Basis JDK1.8b86 und IntelliJ 12
� Veröffentlichungen zu Java 7 u. Java 8:
3
Co
pyr
igh
t©
201
4 A
ccso
Gm
bHDer steile und steinige Weg zu Java 8
(c) Helge Schütt, 2013, Hafencity HH
Java 8 GA seit
18. März 2014!
Java 6: Dezember 2006
Java 7: Juli 2011
Größte Neuerung und erste
wesentliche Sprachänderung
seit den Generics in Java 5:
Lambda Expressions
Umfrage von Typesafe im Feb‘14:
Größte Vorfreude bei Entwicklern
Lambdas mit 83%
Collections/Streams mit 30%
4
Co
pyr
igh
t©
201
4 A
ccso
Gm
bHAgenda: Was kommt in Java 8 mit JSR 335?
Lambda-Ausdrücke
Verbesserte Typinferenz
Methodenreferenzen
Collections & Streams
Filter/Map/Reduce, Parallelisierung
Funktionale InterfacesDefault-
Methoden
in
Interfacesjava.util.function
5
Co
pyr
igh
t©
201
4 A
ccso
Gm
bHAgenda: Was kommt in Java 8 mit JSR 335?
Lambda-Ausdrücke
Verbesserte Typinferenz
Methodenreferenzen
Collections & Streams
Filter/Map/Reduce, Parallelisierung
Funktionale Interfaces
Default-
Methoden
in
Interfaces
java.util.function
Nötig, um in Java 8
die Lambda-Ausdrücke für
Java-Collections nutzbar zu
machen bei gleichzeitiger
Abwärtskompatibilität.
6
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
Was passiert, wenn ich einem Interface eine Methode hinzufügen möchte?
public class FooImpl implements Fooable {void foo() {System.out.println(“Hello!”);
}}
class App { // Version 1public static void main(...) {new FooImpl().foo();
}}
public interface Fooable {void foo(); // Version 1
}
7
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
Was passiert, wenn ich einem Interface eine Methode hinzufügen möchte?
public interface Fooable {void foo(); // Version 2void bar();
}
class App { // Version 2public static void main(...) {new FooImpl().bar();
}}
public class FooImpl implements Fooable {void foo() {System.out.println(“Hello!”);
}}
class App { // Version 1public static void main(...) {new FooImpl().foo();
}}
public interface Fooable {void foo(); // Version 1
}
java.lang.NoSuchMethodError: Fooable.bar()V
8
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
Unterscheide: Laufzeit- und Quelltextsicht bei Kompatibilitätsbetrachtungen von modifizierten Klassen.
Laufzeitsicht� Hinzufügen einer Interface-Methode ist binärkompatibel
� Wir können Fooable (Version 2) kompilieren …
� … und alle Class-Dateien, die das Interface nutzen, linken weiterhin
� VM „webt“ fehlende Methode zur Link-Zeit in die Klassen –diese Methode wirft NoSuchMethodError
Quelltextsicht� FooImpl muss Vertrag von Fooable (Version 2) erfüllen
� Hinzufügen einer Interface-Methode ist nicht abwärtskompatibel
Default-Methodsin Java 8
� Default-Methode ist eine Methodenimplementierung im Interface
� VM „webt“ Default-Methode zur Link-Zeit in implementierende Klasse
� Schlüsselwort default zeigt Default-Methode im Interface an
public interface Fooable {void foo();default void bar() {/* Default-Implementierung im Interface */
}}
Schlüsselwort
default
9
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
Zur Compile-Zeit muss eindeutig sein, welche Default-Methode zur Link-Zeit in Implementierungen genutzt wird.
Regel 1Class wins
� (Super-)Klasse hat immer Vorrang vor Interface („Class wins“)
� Gilt für konkrete und abstrakte Methoden in einer (Super-)Klasse
� Gibt es keine überschriebene Default-Methode in einer (Super)-Klasse, dann greift Regel 2
Regel 2Subtype wins
� Spezifischstes Interface mit Default-Methode wählen („Subtype wins“)
� Beispiel: Default-Methode in List<E> hat Vorrang vor Default-Methode in Collection<E>
� Gibt es mehrere gleich-spezifische Interfaces, dann greift Regel 3
Regel 3� Konflikt kann nicht durch den Compiler aufgelöst werden
� Behandle Default-Methode, als wäre sie abstrakt
� Erfordert Implementierung in konkreter Klasse
Nicht eindeutig?!� Klasse kann mehrere Default-Methoden mit gleicher Signatur haben
� … weil Interface von anderem Interface erben kann
� … weil eine Klasse mehrere Interfaces implementieren kann
� Welche Default-Methode wählt der Compiler?
10
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
Bei eindeutiger Methodenresolution wählt der Compiler die spezifischste Implementierung einer Default-Methode.
public interface A {default void sayHello() {System.out.println(“Hallo aus A”);
}}
public interface B extends A {default void sayHello() {System.out.println(“Hallo aus B”);
}}
public class C1 implements A, B {public static void main(String[] args) {C1 c = new C1();c.sayHello();
}}
Ergebnis:
„Hallo aus B“
B.sayHello ist
spezifischste
Implementierung
aus Sicht von C1!
11
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
Der Compiler zeigt einen Kompilierfehler an, falls er die spezifischste Default-Methode nicht zuordnen kann.
public interface D {default void sayHello() {System.out.println(“D”);
}}
public interface E {default void sayHello() {System.out.println(“E”);
}}
public class C2 implements D, E {public static void main(String[] args) {C2 c = new C2();c.sayHello();
}} C2 kompiliert
nicht, da D und E
„gleich-spezifisch“
12
Co
pyr
igh
t©
201
4 A
ccso
Gm
bHKonflikte muss der Entwickler explizit auflösen.
public interface D {default void sayHello() {System.out.println(“D”);
}}
public interface E {default void sayHello() {System.out.println(“E”);
}}
public class C2 implements D, E {public static void main(String[] args) {C2 c = new C2();c.sayHello();
}
@Overridepublic void sayHello() {D.super.sayHello();
}}
C2 kompiliert nun
wg. expliziter
Auflösung
13
Co
pyr
igh
t©
201
4 A
ccso
Gm
bHIst das nicht Mehrfachvererbung?
Ja, aber…� Mehrfachvererbung gibt es schon immer in Java:
� Klasse B kann von Klasse A ableiten und
� … zusätzliche Interfaces implementieren
� Aber das ist nicht Mehrfachvererbung von Zustand… sondern Mehrfachvererbung von Verhalten!
� Achtung:
� Default-Methoden sind virtuelle Methoden
� Spezifischste Default-Methode wird aus Sicht des dynamischen Typs ermittelt
� Kann zu überraschenden Resultaten führen
14
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
Default-Methoden: Mehrfachvererbung kann zu überraschenden Ergebnissen führen. lc01_interfaces_defaultmethods
Image courtesy of phanlop88 / FreeDigitalPhotos.net
15
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
public interface C extends A {default void result(int i, int j) {System.out.println(“C.result = “ + calc(i, i));
}}
public interface B extends A {default int calc(int i, int j) {return i * j;
}}
public interface C extends A {default void result(int i, int j) {System.out.println(“C.result = “ + calc(i, j));
}}
public interface B extends A {default int calc(int i, int j) {return i * j;
}}
Die Mehrfachvererbung von Verhalten kann zu überraschenden Ergebnissen führen.
public interface A {default void result(int i, int j) {System.out.println(“A.result = “ + calc(i, j));
}default int calc(int i, int j) { return i + j; }
}
public class Impl implements B, C {public static void main(String[] args) {new Impl().result(3, 4); // Ergebnis?
}}
Ergebnis:
12,
nicht 7
17
Co
pyr
igh
t©
201
4 A
ccso
Gm
bHAgenda: Was kommt in Java 8 mit JSR 335?
Lambda-Ausdrücke
Verbesserte Typinferenz
Methodenreferenzen
Collections & Streams
Filter/Map/Reduce, Parallelisierung
Funktionale InterfacesDefault-
Methoden
in
Interfacesjava.util.function
18
Co
pyr
igh
t©
201
4 A
ccso
Gm
bHLambda-Ausdrücke in Java 8
(Object o) -> o.toString()
Eigenschaften� Lambda-Ausdruck ist eine anonyme Methode
� Hat Parameter, einen Rückgabetyp, einen Körper
Operator für Lambda-Ausdrücke
Parameterliste Körper
� Kann den umschließenden Scope nutzen (variable capture)
(Person p) -> p.getName().equals(name)
� Kann existierende Methoden referenzieren
Object::toString
Operator für Methodenreferenzen
19
Co
pyr
igh
t©
201
4 A
ccso
Gm
bHLambda-Ausdrücke in Java 8
Ohne Typangabex -> x + 1;
Mit Typangabe(Integer i) -> list.add(i);
Block, explizites return
(Integer a, Integer b) -> {if (a < b) return a + b;return a;
}
Ohne Parameter() -> System.out.println(“Hallo Lambda!”);
20
Co
pyr
igh
t©
201
4 A
ccso
Gm
bHWozu benötigen wir Lambda-Ausdrücke in Java?
Code-as-Data� Verhalten in Form eines Lambda-Ausdrucks kodieren…
� … und als Parameter an eine generische Methode wie map oder filter übergeben
� Entkoppelt Kodierung von Ausführung (vgl. innere Klassen)
Lesbarkeit� Code ist Kommunikationsmittel
� Beschreibt das „was“, nicht das „wie“
� Mächtigere, ausdrucksstärkere APIs möglich
FunktionalerProgrammierstil
� Höherer Abstraktionsgrad durch Funktionen höherer Ordnung
� Eleganter, kürzer, prägnanter
� Keine Seiteneffekte
21
Co
pyr
igh
t©
201
4 A
ccso
Gm
bHLambda-Ausdrücke ersetzen anonyme innere Klassen.
Bedeutung für bisherigen Code
� Äquivalenz zwischen Lambdas und anonyme innerer Klasse mit Single-Abstract-Method (SAM).
� Beispiele: Runnable, Comparator, ActionListener, …
Lambda� Führt keinen zusätzlichen Scope ein
� Damit kein Shadowing von Bezeichnern
� this bezieht sich auf die umschließende Klasse
� Ein return aber nicht!
Anonyme innere Klasse
� Führt zusätzlichen Scope ein
� Daher Shadowing von Bezeichnern in verschiedenen Scopes
� this bezieht sich auf anonyme innere Klasse,umschließender Kontext über ClassName.this erreichbar
22
Co
pyr
igh
t©
201
4 A
ccso
Gm
bHVon Anonymous Inner Classes zu Lambdas
lc02_anoninnerclass
Image courtesy of phanlop88 / FreeDigitalPhotos.net
23
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
Anonyme innere Klassen lassen sich einfach in äquivalente Lambda-Ausdrücke migrieren.
Comparator<Integer> sortAscending = new Comparator<Integer>() {@Overridepublic int compare(Integer a, Integer b) {return a-b;
}};
Typischer Code für einen Comparator, der Ganzzahlen vergleicht …
Comparator<Integer> sortAscending = new Comparator<Integer>() {@Overridepublic int compare(Integer a, Integer b) {return a-b;
}};
Warum muss ich compare
überschreiben? Es ist die
einzige Methode des Interface,
mein Vorhaben ist doch klar?
24
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
Anonyme innere Klassen lassen sich einfach in äquivalente Lambda-Ausdrücke migrieren.
Comparator<Integer> sortAscending = new Comparator<Integer>() {@Overridepublic int compare(Integer a, Integer b) {return a-b;
}};
Comparator<Integer> sortAscending = (Integer a, Integer b) -> {return a-b;
};
Reduktion auf das Wesentliche:
Eingabedaten und Code-Block
Die Definition eines Comparator-Interfaces erfordert viel Boilerplate-Code
Comparator<Integer> sortAscending = new Comparator<Integer>() {@Overridepublic int compare(Integer a, Integer b) {return a-b;
}};
25
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
Anonyme innere Klassen lassen sich einfach in äquivalente Lambda-Ausdrücke migrieren.
Comparator<Integer> sortAscending = (Integer a, Integer b) -> {return a-b;
};
Comparator<Integer> sortAscending = (Integer a, Integer b) -> a-b;
Comparator<Integer> sortAscending = (a, b) -> a-b;
… wir führen allerdings immer noch redundante Informationen mit …
Block-Schreibweise und
explizites return entfernen
Der Compiler kann Typ-
informationen inferieren
27
Co
pyr
igh
t©
201
4 A
ccso
Gm
bHPerformance?
Entnommen aus Joseph D. Darcy‘s Präsentation On the road to JDK 8 @ Devoxx 2012, Antwerpen
Operationen / s Single-threaded Multi-threaded
(saturiert)
Faktor
Anonyme innere Klasse 160 1407 8,8
Capturing Lambda 160 1400 8,8
Non Capturing Lambda 636 23201 36,4
Performance� Lambda-Funktionen sind im Worst-Case so effizient wie
anonyme innere Klassen, im Best-Case deutlich schneller.
� Oracle Performance Team hat Effizienzbetrachtungen durchgeführt (Daten von Herbst 2012).
Lambdas ohne Zugriff
auf umschließenden
Kontext: Um Faktoren
schneller!
28
Co
pyr
igh
t©
201
4 A
ccso
Gm
bHPerformance?
Entnommen aus Joseph D. Darcy‘s Präsentation On the road to JDK 8 @ Devoxx 2012, Antwerpen
Kosten Anonyme innere Klasse Lambda-Ausdruck
Link-Zeit Laden der Klasse Einmalige Kosten beim Aufsetzender capture
Konstruktion Konstruktor aufrufen Erzeugung des Lambda-Ausdrucks
Aufruf invokeinterface invokedynamic
29
Co
pyr
igh
t©
201
4 A
ccso
Gm
bHAgenda: Was kommt in Java 8 mit JSR 335?
Lambda-Ausdrücke
Verbesserte Typinferenz
Methodenreferenzen
Collections & Streams
Filter/Map/Reduce, Parallelisierung
Funktionale InterfacesDefault-
Methoden
in
Interfacesjava.util.function
30
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
Funktionale Interfaces repräsentieren das Typsystem für einen Lambda-Ausdruck.
Funktionale Interfaces
� Single-Abstract-Method-Typen (SAM-Typen)
� Darf nur eine abstrakte Methode enthalten
� … aber beliebig viele Default-Methoden
� Annotation java.lang.FunctionalInterface
� Beispiele: Runnable, Comparator, ActionListener, …
Evaluation eines Lambda-Ausdrucks
� Funktionales Interface ist sog. Target Type für Lambda-Ausdruck
� Rückgabetyp der abstrakten Methode
� Parametertypen der abstrakten Methode
� Deklarierte Exceptions der abstrakten Methode
� Verbesserte Typinferenz von Java 8 bringt Lambda und Interface zusammen
31
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
Ein Lambda-Ausdruck evaluiert zu einem funktionalen Interface.
Comparator<Integer> sortAscending = (Integer a, Integer b) -> a-b;
@FunctionalInterfacepublic interface Comparator<T>() {
int compare(T o1, T o2);...
};
Rückgabetyp stimmt überein
Parametertyp stimmt überein
32
Co
pyr
igh
t©
201
4 A
ccso
Gm
bHAgenda: Was kommt in Java 8 mit JSR 335?
Lambda-Ausdrücke
Verbesserte Typinferenz
Methodenreferenzen
Collections & Streams
Filter/Map/Reduce, Parallelisierung
Funktionale InterfacesDefault-
Methoden
in
Interfacesjava.util.function
33
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
Im Package java.util.function sind funktionale Interfaces, die für typische Use-Cases bestimmt sind.
Neue funktionale Interfaces
1. Function - Zur Abbildung von Funktionen
2. Consumer - Zur Abbildung von Prozeduren mit Seiteneffekten
3. Predicate - Zur Abbildung von Prädikaten
4. Supplier - Zur Abbildung von Objektfabriken
Neue Klasse Optional<T>
� Vereinfacht Null-Behandlung im Code
� Methoden können einfach Optional<T> liefern
� Optional bietet viele Convenience-Methoden an
� Arbeiten mit funktionalen Interfaces zusammen
@NotNull Optional<T> opt = ...opt.ifPresent(t -> doSomething(t));
34
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
1) Das funktionale Interface Function<T,R> ist ein Transformator eines Eingabetyps T in einen Ausgabetyp R.
@FunctionalInterfacepublic interface Function<T, R> {
public R apply(T t);
public default <V> Function<T, V> compose(Function<? super R, ? extends V> after)
{Objects.requireNonNull(after);return (T t) -> after.apply(apply(t));
}}
Function<Integer, Integer> square = n -> n*n;assertEquals(9, square.apply(3));
Definition
Einfaches Beispiel
Definition
Evaluation
35
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
1) Ein typisches Nutzungsszenario für solcheFunction<T,R> -Instanzen sind Transformatoren bei map()
public static <I, O> List<O> map(Function<I, O> mapper, List<I> input) {final List<O> outputList = new ArrayList<>();input.forEach(i -> {O o = mapper.apply(i);outputList.add(o);
});return outputList;
}
Code-as-Data� Nicht Function<T,R>.apply(arg) direkt aufrufen, …
� … sondern Function<T,R> als Lambda-Ausdruck übergeben!
map(x -> Integer.valueOf(x), input);
generische Funktion Lambda-Ausdruck Daten
36
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
2) Ein Consumer<T> ist eine Funktion, die den Programmzustand durch Seiteneffekte verändert.
@FunctionalInterfacepublic interface Consumer<T> {
public void accept(T t);
public default Consumer<T> chain(Consumer<? super T> other)
{Objects.requireNonNull(other);return (T t) -> { accept(t); other.accept(t);};
}}
Definition
Eigenschaften� Analog zu Function<T,R>,
aber keine Rückgabe sondern Seiteneffekt
� Mehrere Consumer<T> kann man miteinander verketten -Ausführung von links nach rechts
Einfaches BeispielConsumer<String> out = s -> System.out.println(s);out.accept("Test");
37
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
2) Ein typisches Nutzungsszenario für Consumer<T> -Instanzen ist die Verarbeitung von Collections mit forEach.
public static <T> void forEach(Consumer<T> consumer, List<T> input) {for (T t : input) {consumer.accept(t);
}}
Code-as-Data� Nicht Consumer<T>.accept(arg) direkt aufrufen, …
� … sondern Consumer<T> als Lambda-Ausdruck übergeben!
forEach(s -> System.out.println(s), input);
generische Funktion Lambda-Ausdruck Daten
38
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
3) Ein Predicate<T> ist eine Funktion, die ein Objekt vom Typ T einem logischen Test unterzieht.
@FunctionalInterfacepublic interface Predicate<T> {
public boolean test(T t);
public default Predicate<T> and(...) { ... }public default Predicate<T> negate() { ... }public default Predicate<T> or(...) { ... }public default Predicate<T> xor(...) { ... }
}
Definition
39
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
3) Ein typisches Nutzungsszenario für Predicate<T>-Instanzen ist die Filterung von Collections mit filter()
public static <T> List<T> filter(Predicate<T> criterion, List<T> input) {final List<T> filteredList = new ArrayList<>();input.forEach(i -> {if (criterion.test(i)) {filteredList.add(i);
}});return filteredList;
}
Code-as-Data� Nicht Predicate<T>.test(arg) direkt aufrufen, …
� … sondern Predicate<T> als Lambda-Ausdruck übergeben!
filter(n -> n % 2 == 0, input);
generische Funktion Lambda-Ausdruck Daten
40
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
3) Predicate<T>-Instanzen kann man zu komplexeren Testkriterien verknüpfen. lc03_predicates
Image courtesy of phanlop88 / FreeDigitalPhotos.net
41
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
3) Predicate<T>-Instanzen kann man zu komplexeren Testkriterien verknüpfen.
Einfaches BeispielPredicate<Integer> isEven = n -> n % 2 == 0;assertTrue(isEven.test(2));
Predicate<Integer> isOdd = isEven.negate();assertTrue(isOdd.test(3));
Negation
Und-VerknüpfungPredicate<Integer> isPositive = n -> n > 0;Predicate<Integer> isEvenAndPositive =
isEven.and(isPositive);assertTrue(isEvenAndPositive.test(2));assertFalse(isEvenAndPositive.test(0));
Oder-VerknüpfungPredicate<Integer> isZero = n -> n == 0;Predicate<Integer> isZeroOrPositive =
isPositive.or(isZero);assertTrue(isZeroOrPositive.test(0));
43
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
4) Ein Supplier<T> ist eine Factory-Funktion, die Objekte vom Typ T liefert.
@FunctionalInterfacepublic interface Supplier<T> {public T get();
}
Definition
Einfaches BeispielSupplier<Integer> generator =
() -> (int) (Math.random() * NUMBER_RANGE);int randomNumber = generator.get();assertTrue(
randomNumber > 0 && randomNumber <= NUMBER_RANGE);
Nutzung?� Zufallszahlengeneratoren braucht man jetzt nicht so oft …
� Am ehesten als …
� Closure um Collection, aus der Elemente geholt werden
� Objektfabrik
44
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
java.util.function enthält weitere funktionale Interfaces - im Wesentlichen syntaktischer Zucker…
Binäre
FunktionenBiConsumer
BiFunction
BiPredicate
Typ-
parametrierte
Funktionen
BinaryOperator
…
IntConsumer
DoubleFunction
LongFunction
…
LongBinary-
Operator
DoubleBinary-
Operator
…
@FunctionalInterfacepublic interface BinaryOperator<T> extends BiFunction<T,T,T> {}
45
Co
pyr
igh
t©
201
4 A
ccso
Gm
bHAgenda: Was kommt in Java 8 mit JSR 335?
Lambda-Ausdrücke
Verbesserte Typinferenz
Methodenreferenzen
Collections & Streams
Filter/Map/Reduce, Parallelisierung
Funktionale InterfacesDefault-
Methoden
in
Interfacesjava.util.function
46
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
Das Design der Streams-API basiert auf einer Pipes-and-Filters-Architektur.
ps –ef | grep login | cut –c 50- | head
Decrypt Authenticate De-DupPipePipe Pipe Pipe
Filter Filter FilterIncoming
Order
Clean
Order
Verkettung von Unix-Programmen
Enterprise Integration Patterns
47
Co
pyr
igh
t©
201
4 A
ccso
Gm
bHInterne Iteration, Streams, Filter/Map/Reduce
lc04_streams
Image courtesy of phanlop88 / FreeDigitalPhotos.net
48
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
Externe Iteratoren sind einfach zu benutzen, dafür aber inhärent sequentiell und nicht zusammensetzbar.
List<Integer> prices = Arrays.asList(10, 20, 30, 40, 50);double totalCost = 0.0;for (Integer price : prices) {totalCost += price;
}return totalCost;
List<Integer> prices = Arrays.asList(10, 20, 30, 40, 50);double totalCost = 0.0;for (Integer price : prices) {totalCost += price * 0.9;
}return totalCost;
List<Integer> prices = Arrays.asList(10, 20, 30, 40, 50);double totalCost = 0.0;for (Integer price : prices) {if (price >= 40)totalCost += price * 0.9;
}return totalCost;
List<Integer> prices = Arrays.asList(10, 20, 30, 40, 50);double totalCost = 0.0;for (Integer price : prices) {if (price >= 40)totalCost += price * 0.9;
}return totalCost;
Eigenschaften� Nicht parallelisierbar
� Nicht zusammensetzbar
� Iteration über Preise
� Filterung nach bestimmten Preisen
� Reduktion um 10% pro Preis
� Summierung der Teilergebnisse
� Reduktion und Summierung auch noch vermischt
49
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
Die Steuerung einer internen Iteration obliegt dem Container. Ein interner Iterator ist zusammensetzbar.
List<Integer> prices = Arrays.asList(10, 20, 30, 40, 50);return prices.stream().sum();
List<Integer> prices = Arrays.asList(10, 20, 30, 40, 50);return prices.stream().mapToDouble(price -> price * 0.9).sum();
List<Integer> prices = Arrays.asList(10, 20, 30, 40, 50);return prices.stream().filter(price -> price >= 40).mapToDouble(price -> price * 0.9).sum();
List<Integer> prices = Arrays.asList(10, 20, 30, 40, 50);return prices.stream().filter(price -> price >= 40).mapToDouble(price -> price * 0.9).sum();
Eigenschaften� Lambda-Funktion kodiert Verhalten …
� … wird an generische Methode des Containers übergeben
� Container entscheidet, ob sequentiell oder parallel und / oder lazy
� Dekomposition in einzelne, funktionale Aspekte
� Schritt 1, Schritt 2, …, Schritt k
� „Fluss“ ist sofort erkennbar
51
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
Das Design der Streams-API basiert auf einer Pipes-and-Filters-Architektur.
prices.stream().filter(p -> p >= 40).mapToDouble(p -> p * 0.9).sum();
Source
52
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
Das Design der Streams-API basiert auf einer Pipes-and-Filters-Architektur.
prices.stream().filter(p -> p >= 40).mapToDouble(p -> p * 0.9).sum();
Source IntermediateOp
Stream<Integer>
53
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
Das Design der Streams-API basiert auf einer Pipes-and-Filters-Architektur.
prices.stream().filter(p -> p >= 40).mapToDouble(p -> p * 0.9).sum();
Source IntermediateOp
Stream<Integer>
IntermediateOp
Stream<Integer>
54
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
Das Design der Streams-API basiert auf einer Pipes-and-Filters-Architektur.
prices.stream().filter(p -> p >= 40).mapToDouble(p -> p * 0.9).sum();
Source IntermediateOp
Stream<Integer>
IntermediateOp
Stream<Integer>
TerminalOp
Stream<Double> Double
55
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
Intermediäre und terminale Operationen sind Wrapper um einen Lambda-Ausdruck.
prices.stream().filter(p -> p >= 40).mapToDouble(p -> p * 0.9).sum();
Source FilterOp
Stream<Integer>
IntermediateOp
Stream<Integer>
TerminalOp
Stream<Double> Double
Instanz von Predicate<Integer>
56
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
Intermediäre und terminale Operationen sind Wrapper um einen Lambda-Ausdruck.
prices.stream().filter(p -> p >= 40).mapToDouble(p -> p * 0.9).sum();
Source FilterOp
Stream<Integer>
MapOp
Stream<Integer>
TerminalOp
Stream<Double> Double
Instanz von Predicate<Integer>
Instanz von Instanz von ToDoubleFunction
<Integer>
57
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
Intermediäre und terminale Operationen sind Wrapper um einen Lambda-Ausdruck.
prices.stream().filter(p -> p >= 40).mapToDouble(p -> p * 0.9).sum();
Source FilterOp
Stream<Integer>
MapOp
Stream<Integer>
ReduceOp
Stream<Double> Double
Instanz von Predicate<Integer>
Instanz von Instanz von ToDoubleFunction
<Integer>
Instanz von DoubleBinaryOperator
58
Co
pyr
igh
t©
201
4 A
ccso
Gm
bHDie Auswertung der Operationen erfolgt lazy bzw. eager.
prices.stream().filter(p -> p >= 40).mapToDouble(p -> p * 0.9).sum();
Source FilterOp
Stream<Integer>
MapOp
Stream<Integer>
ReduceOp
Stream<Double> Double
Instanz von Predicate<Integer>
Instanz von Instanz von ToDoubleFunction
<Integer>
Instanz von DoubleBinaryOperator
lazy lazy eager
59
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
Die Auswertung von intermed. Operationen erfolgt erst, wenn der Stream durch eine terminale Operation angestossen wird.
String[] txt = { "State", "of", "the", "Lambda", "Libraries", "Edition" };IntStream is =
Arrays.stream(txt).filter(s -> s.length() > 3).mapToInt(s -> { System.out.println(s + ","); return s.length(); });
// erste terminale Operationis.forEach(l -> System.out.print(l + ", "));
Mit der Variable is halten und nutzen wir eine Referenz auf den Stream. Das ist schlechter Stil und potentiell gefährlich, denn zwei oder mehr terminale Operationen könnten darauf aufgerufen werden, was nicht erlaubt ist.
Nur eine terminale Operation ist möglich! Danach ist der Stream “verbraucht”.
60
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
String[] txt = { "State", "of", "the", "Lambda", "Libraries", "Edition" };IntStream is =
Arrays.stream(txt).filter(s -> s.length() > 3).mapToInt(s -> { System.out.println(s + ","); return s.length(); });
// erste terminale Operationis.forEach(l -> System.out.print(l + ", "));
// zweite terminale Operationint sum = is.reduce(0, (l1, l2) -> { return (l1 + l2); });
Laufzeitfehler daher bei der zweiten terminalen Operation!
Exception in thread "main" java.lang.IllegalStateEx ception: stream has already been operated uponat java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:221)at java.util.stream.IntPipeline.reduce(IntPipeline.java:453)...
Die Auswertung von intermed. Operationen erfolgt erst, wenn der Stream durch eine terminale Operation angestossen wird.
61
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
Die Streams-API bietet eine Fülle generischer Methoden, die wir zur Verarbeitung von Collections nutzen können.
Operation Rückgabe Evaluation Interface λ-Signatur
filter Stream<T> lazy Predicate<T> T � boolean
map Stream<R> lazy Function<T, R> T � R
reduce T eager BinaryOperator<T> (T, T) � T
limit Stream<T> lazy - -
findFirst Optional<T> eager - -
forEach void eager Block<T> T � void
sorted Stream<T> lazy Comparator<T> (T, T) � boolean
collect Collection<R> eager - -
anyMatch boolean eager Predicate<T> T � boolean
peek Stream<T> lazy - -
... ... ... ... ...
62
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
Die Streams-API bietet eine Fülle generischer Methoden, die wir zur Verarbeitung von Collections nutzen können.
Operation Rückgabe Evaluation Interface λ-Signatur
filter Stream<T> lazy Predicate<T> T � boolean
map Stream<R> lazy Function<T, R> T � R
reduce T eager BinaryOperator<T> (T, T) � T
limit Stream<T> lazy - -
findFirst Optional<T> eager - -
forEach void eager Block<T> T � void
sorted Stream<T> lazy Comparator<T> (T, T) � boolean
collect Collection<R> eager - -
anyMatch boolean eager Predicate<T> T � boolean
peek Stream<T> lazy - -
... ... ... ... ...
peek() ist nützlich zum Debuggen. Wie geht das sonst?!?!?...List<Integer> prices = Arrays.asList(10, 20, 30,40, 50, 60, 70); return prices.stream()
.peek(price -> System.out.println(price))
.filter(price -> price >= 40) // Filter
.peek(price -> System.out.println(price))
.mapToDouble(price -> price * 0.9) // Map: Rabatt
.peek(price -> System.out.println(price))
.sum ();
63
Co
pyr
igh
t©
201
4 A
ccso
Gm
bHAgenda: Was kommt in Java 8 mit JSR 335?
Lambda-Ausdrücke
Verbesserte Typinferenz
Methodenreferenzen
Collections & Streams
Fehlerbehandlung
Funktionale InterfacesDefault-
Methoden
in
Interfacesjava.util.function
64
Co
pyr
igh
t©
201
4 A
ccso
Gm
bHFehlerbehandlung in Streams
lc05_streams_errorhandling
Image courtesy of phanlop88 / FreeDigitalPhotos.net
65
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
Error Handling in Streams (1/3)ohne Exception-Handling in try/catch
String[] txt = { "abc", "abcde", "abc" };
// Stream wird aufgebautStream<Character> s1 = Arrays.stream(txt).
map(t -> {// gezielte Exception fuer alle Strings mit Laenge > 3if (t.length()>3) throw new RuntimeException("test too long");return t.charAt(0);
});
// Stream wird konsumierts1.forEach( c -> System.out.print(c.charValue() + ",") );
Ausgabe: a, danach Exception (da abcde zu lange ist).
abc wird nicht mehr verarbeitet.
66
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
Error Handling in Streams (2/3)Exception-Handling in der intermediären Operation
String[] txt = { "rst", "rstuv", "rst" };
// Stream wird aufgebautStream<Character> s2 = Arrays.stream(txt).
map(t -> {try {
if (t.length()>3) throw new RuntimeException("test too long");
return t.charAt(0);}catch (Exception ex) {
return null;}
});
// Stream wird konsumierts2.forEach(c -> { if (c!=null) System.out.print(c.charValue() + ","); });
Ausgabe: r und r
67
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
Error Handling in Streams (3/3)Exception-Handling in der terminalen Operation
String[] txt = { "xyz", "vwxyz", "xyz" };
// Stream wird aufgebautStream<Character> s3 = Arrays.stream(txt).
map(t -> {if (t.length()>3) throw new RuntimeException("test too long");return t.charAt(0);
});
// Stream wird konsumierts3.forEach( c -> {
try {System.out.print(c.charValue() + ",");
}catch (Exception ex) {
ex.printStackTrace(System.err);}
} );
Ausgabe: x, danach Exception (da vwxyz zu lange ist).
xyz wird nicht mehr verarbeitet.
69
Co
pyr
igh
t©
201
4 A
ccso
Gm
bHAgenda: Was kommt in Java 8 mit JSR 335?
Lambda-Ausdrücke
Verbesserte Typinferenz
Methodenreferenzen
Collections & Streams
Parallelisierung
Funktionale InterfacesDefault-
Methoden
in
Interfacesjava.util.function
70
Co
pyr
igh
t©
201
4 A
ccso
Gm
bHWie kann man Programme überhaupt parallelisieren?
Schritt 1
Schritt 2
Schritt 3
Schritt 4
Schritt 5
Schritt 6
Variante 1: Parallelisierung des Datenflussesfo
r 1
... k
do
Schritt 1
Schritt 2
Schritt 3
Schritt 4
Schritt 5
Schritt 6
for
each
ele
men
t d
o in
par
alle
l Schritt 1
Schritt 2
Schritt 3
Schritt 4
Schritt 5
Schritt 6
Schritt 1
Schritt 2
Schritt 3
Schritt 4
Schritt 5
Schritt 6
Schritt 1
Schritt 2
Schritt 3
Schritt 4
Schritt 5
Schritt 6
......
71
Co
pyr
igh
t©
201
4 A
ccso
Gm
bHWie kann man Programme überhaupt parallelisieren?
Schritt 1
Schritt 2
Schritt 3
Schritt 4
Schritt 5
Schritt 6
Schritt 1
Schritt 2 Schritt 3
4a 4b 4c
Schritt 5
Schritt 6
Variante 2: Parallelisierung des Kontrollflusses
72
Co
pyr
igh
t©
201
4 A
ccso
Gm
bHWie kann man Programme überhaupt parallelisieren?
Schritt 1
Schritt 2
Schritt 3
Schritt 4
Schritt 5
Schritt 6
Schritt 1
Schritte 2-5
Schritt 6
2-5.1 2-5.2 2-5.n
2-5.1.1 2-5.1.2 2-5.1.n
Variante 2b: rekursive Parallelisierung des Kontrollflusses
Fork/Join mit Teile&Herrsche!
73
Co
pyr
igh
t©
201
4 A
ccso
Gm
bHFork-Join adressiert leicht zu parallelisierende Probleme
Teile und Herrsche
� Zerlege Problem sukzessive in Teilprobleme
� ... bis Teilproblem klein genug, so dass es direkt gelöst werden kann
� ... und führe dann die Ergebnisse zusammen
� „embarassingly parallel“
// join
loeseProblemMitForkJoin (Problem p)
if (p ist klein genug)
loese p direkt und sequentiell
else
teile p in unabhängige Teilprobleme p1 und p2 / / split
loese p1 und p2 unabhängig voneinander (rekursiv) / / fork
führe Teilergebnisse von p1 und p2 zusammen // join
74
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
Fork-Join-Baum der Aufgaben. Basis der Parallelisierung ist Fork/Join-Framework aus Java7 (JSR166y).
T
rekursive Aufteilung in
Teilprobleme in der Fork-Phase
Zusammenführung der
Teilergebnisse in der Join-Phase
Problem klein
genug?
Sequentielle
Berechnung
T1
T2
T1
T2
T1
T2
T1
T2
TT
t
T11
T21
T22
T12
T11
T21
T22
T12
75
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
Sequentielle und parallele Streams:Basis der Parallelisierung ist Fork/Join aus Java7 (JSR166y)
String[] txt = { "State", "of", "the", "Lambda", "Libraries", "Edition" };IntStream is =
Arrays.stream(txt).filter(s -> s.length() > 3).mapToInt(s -> s.length());
int sum = is.reduce(0, (l1, l2) -> { return (l1 + l2); });
String[] txt = { "State", "of", "the", "Lambda", "Libraries", "Edition" };IntStream is =
Arrays.stream(txt).parallel(). // auch: Collection.parallelStream()filter(s -> s.length() > 3).mapToInt(s -> s.length());
int sum = is.reduce(0, (l1, l2) -> { return (l1 + l2); });
76
Grenzen der Parallelisierung: Unterscheide zustandslose von zustandsbehafteten intermediären Operationen
Zustandslose intermediäreOperationen
� Zustandslose intermediäre Operationen bearbeiten ein einzelnes Element
� Beispiele
� filter
� map
� Problemlos parallelisierbar, keine Synchronisierung erforderlich
� Zustandsbehaftete intermediäre Operationen benötigen einen zusätzlichen Kontext
� Beispiele
� limit nur die ersten k Elemente
� distinct nur disjunkte Elemente
� sorted Sortierung, siehe Folgefolie
� Schwierig parallelisierbar
Zustandsbehaftete intermediäreOperationen
77
Was passiert in einem ParallelStream bei sorted()?
...stream().parallel() .statelessOps() .sorted() .statelessOps() .terminal()
Barriere: Parallelisierung
wird hier gezielt
sequentialisiert.
Ergebnisse werden
zwischengepuffert (auch
bei seq. Stream!)
Parallelisierung
wird neu aufgesetzt
Sequentielle
Bearbeitung
(in einem Thread).
78
Beispiel „Finde das maximale Elemente mit einem Parallel-Stream“: Vorsicht beim Performance-Vergleich!
T
T1
T2
T1
T2
T1
T2
T1
T2
TT
T11
T21
T22
T12
[1, n]
[1,�
�]
[�
�+ 1, n]
max(T11,T12)
max(T21,T22)
max(T1,T2)
[1,�
�]
[�
�+ 1,
�
�]
[�
�+ 1,
��
�]
[��
�+ 1, n]
int[] ints = { 17, 453, 5, 1, 458, 48, 6, 99, /* ... etc. ... */ };int max = Arrays.stream(ints).parallel().
reduce(Integer.MIN_VALUE, (i,j) -> Math.max(i,j));System.out.println(max);
79
Performance bei der Parallelisierung in Streams: Old Style vs. Seq. Streams vs. Parallele Streams
Image courtesy of phanlop88 / FreeDigitalPhotos.net
lc06_streams_parallel
80
int[] ints = new int[64] ;for (int i=0; i<64; i++) ints[i] = i;
List<String> stringList= new LinkedList<>();
Arrays.stream(ints).parallel().mapToObj(String::valueOf)
.forEach(stringList::add);
System.out.println(stringList);
int[] ints = new int[64] ;for (int i=0; i<64; i++) ints[i] = i;
List<String> stringList= new LinkedList<>();
Arrays.stream(ints).parallel().mapToObj(String::valueOf).sequential().forEach(stringList::add);
System.out.println(stringList);
Sequentieller/Paralleler Moduswechsel und -Check im Stream selbst möglich!
Methoden� Stream.isParallel() Check, ob Stream parallel ist
� Stream.parallel() Modifikation des Streams ...
� Stream.sequential() ... zu par./seq. Stream möglich
int[] ints = new int[64] ;for (int i=0; i<64; i++) ints[i] = i;
List<String> stringList = new LinkedList<>();
Arrays.stream(ints) .mapToObj(String::valueOf).forEach(stringList::add);
System.out.println(stringList);
Achtung: Nicht thread-safe!
81
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
Literatur zum Thema von uns bei JavaSPEKTRUM und Heise Developer… und die heutigen Folien auf accso.de
Markus Günther, Martin Lehmann„Lambda-Ausdrücke in Java 8“.
In JavaSPEKTRUM, 03/2013PDF-Download hier:http://www.sigs-datacom.de/fachzeitschriften/javaspektrum/archiv/artikelansicht.html?tx_mwjournals_pi1[pointer]=0&tx_mwjournals_pi1[mode]=1&tx_mwjournals_pi1[showUid]=7237
Markus Günther, Martin Lehmann„Java 7: Das Fork-Join-Framework für mehr Performance“
In JavaSPEKTRUM, 05/2012PDF-Download hier:
http://www.sigs-datacom.de/fachzeitschriften/javaspektrum/archiv/artikelansicht.html?tx_mwjournals_pi1[pointer]=0&tx_mwjournals_pi1[mode]=1&tx_mwjournals_pi1[showUid]=7396
Markus Günther, Martin Lehmann„Streams und Collections in Java 8“.
Heise Developer, http://www.heise.de/developer/artikel/Streams-und-
Collections-in-Java-8-2151376.html
82
Co
pyr
igh
t©
201
4 A
ccso
Gm
bH
Weitere Links und Literatur zu Java 8
https://jdk8.java.net/
http://www.techempower.com/blog/2013/03/26/everything-about-java-8/
http://www.heise.de/developer/artikel/Was-Entwickler-mit-Java-8-erwartet-1932997.html
Weitere Links