Programmierung
Anonyme Klassen und Lambda-Ausdrücke in Java
Prof. Dr. Michael Goedicke
Erinnerung: Anonyme Klassen
▪ Java ermöglicht es, Klassen zu deklarieren und gleichzeitig zu instanziieren
▪ diese Klassen werden nur einmalig verwendet
▪ sind unbenannt, daher der Name „Anonyme Klasse“
▪ Sinn dahinter: „Einweg-Klassen“, die nur an einer Stelle benötigt
werden
▪ Vermeidung unnötiger Klassen im Namensraum (aufgeräumterer Code) ▪ spontane, einmalige Implementierung von Interfaces
▪ Syntax
▪ new AnonymousClass() {
// Definitionen, Methoden, etc.
};
M. Goedicke – Programmierung WiSe 2020/2021
2
Anonyme Klassen: Scope
▪ Anonyme Klassen kennen den Kontext, in dem sie definiert wurden ▪ Anonyme Klassen haben Zugriff auf
▪ Felder der sie umgebenen Klasse
▪ als final markierte lokale Variablen innerhalb einer Methode, in der sie
definiert wurden
▪ …haben jedoch auch Einschränkungen, u.a.
▪ statische Variablen müssen als final deklariert sein ▪ Definition von Konstruktoren nicht möglich
M. Goedicke – Programmierung WiSe 2020/2021
3
Anonyme Klassen: Beispiel Listener (“klassisch”)
public class MyFrame extends JFrame { private JButton saveButton;
private JButton openButton;
public MyFrame() {
saveButton.addActionListener(new SaveButtonListener()); openButton.addActionListener(new OpenButtonListener());
} }
class SaveButtonListener implements ActionListener { @Override
public void actionPerformed(ActionEvent e) { // …
} }
class OpenButtonListener implements ActionListener { // analog zu SaveButtonListener
}
M. Goedicke – Programmierung WiSe 2020/2021
4
Anonyme Klassen: Beispiel Listener (anonym)
public class MyFrame extends JFrame { private JButton saveButton;
private JButton openButton;
public MyFrame() {
saveButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) { // …
} });
openButton. addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) {
// … }
}); }
}
M. Goedicke – Programmierung WiSe 2020/2021
5
Anonyme Klassen: Beispiel Listener (Scope)
public class MyFrame extends JFrame { private String windowTitle = “My Frame”;
private JButton saveButton; private JButton openButton;
public MyFrame() {
final int numberOfButtons = 2;
saveButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) {
System.out.println(numberOfButtons);
System.out.println(windowTitle); }
});
// usw. }
}
M. Goedicke – Programmierung WiSe 2020/2021
6
Nachteile Anonymer Klassen
▪ Trotz genannter Vorteile bringen Anonyme Klassen immer noch
einige unschöne Effekte mit sich
▪ Gerade für Interfaces mit nur einer Methode (bspw. Comparator
ActionListener) immer noch umständliche Syntax:
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println(“Klick.”);
}
}); // 4 Zeilen Code für ein System.out.println() …
▪ Lokale Variablen, die nicht als final deklariert sind, können nicht verwendet werden
M. Goedicke – Programmierung WiSe 2020/2021
7
Lambda in Java
▪ Mit Erscheinen von Java 8 erhalten sogenannte Lambda-Ausdrücke Einzug in die Sprache
▪ Einführung von funktionalen Konstrukten
▪ weitere Vereinfachung von Code
▪ Vereinfachte Anwendung von Nebenläufigkeit
▪ Lambda-Ausdrücke ersetzen elegant die Definition anonymer Klassen für Interfaces mit genau einer Methode
▪ sogenannte SAM-Interfaces (Single Abstract Method)
▪ Weite Teile der Java-Standardbibliothek ist für Java 8 für den Einsatz
von Lambda-Ausdrücken angepasst worden
▪ besonders das Java-Collections-Framework ist für uns von Bedeutung
M. Goedicke – Programmierung WiSe 2020/2021
8
Grundlagen
▪ Lambda-Kalkül (auch λ-Kalkül) in den 1930er Jahren von Alonzo Church formuliert
▪ relativ einfaches formales Modell ▪ untypisiert
▪ λ-Kalkül kennt nur anonyme Funktionen
▪ benannte Funktionen können anonymisiert werden
▪𝑖𝑑𝑥 =𝑥 ▪𝑠𝑞𝑟𝑥 =𝑥2 ▪𝑎𝑑𝑑 𝑥,𝑦 =𝑥+𝑦
𝑥↦𝑥 𝑥⟼𝑥∗𝑥
𝑥,𝑦 ⟼𝑥+𝑦
▪ Funktionen sind immer einstellig
▪ Funktionen mit mehreren Parametern werden durch sogenanntes
Currying verkuppelt ▪(𝑥⟼ 𝑦⟼𝑥+𝑦)
▪ dies erzeugt Funktionen höherer Ordnung, d.h. Funktionen, die wiederum Funktionen zurückgeben
M. Goedicke – Programmierung WiSe 2020/2021
9
Grundlagen
▪ Lambda-Kalkül kennt zwei Bestandteile ▪ Funktionsdefinition
▪ Funktionsapplikation
▪ zur Berechnung von Ergebnissen werden Argumente (Parameter) auf Funktionen angewandt (Funktionsapplikation)
▪ 𝑥 ⟼ x 14 = 14
▪ 𝑥 ⟼ 𝑥 ∗ 𝑥 5 = 5 ∗ 5 = 25
▪ etwas komplizierter bei durch Currying verkuppelten Funktionen
▪((𝑥⟼ 𝑦⟼𝑥+𝑦 )(9))(4)=(𝑦⟼9+𝑦)(4)=9+4=13
M. Goedicke – Programmierung WiSe 2020/2021
10
Lambda in Java
▪ Um das Lambda-Kalkül hat sich eine ganze Gruppe von Programmiersprachen, die funktionalen Sprachen, gebildet
▪ LISP, Scheme, Erlang, Clojure, Haskel, OCaml, Scala, …
▪ auch imperative Sprachen haben funktionale Konzepte übernommen
▪ bspw. C++, C#, Python, Ruby, …
▪ mit Erscheinen von Java 8 hält Lambda-Kalkül Einzug in Java
▪ anonyme Funktionen zur Vereinfachung des Codes
▪ in der Literatur auch als Closures bekannt ▪ vielfältige Anwendungsfälle
▪ Konfiguration von Algorithmen
▪ Callbacks
▪ Iteration durch Collections
▪ Eliminierung von Hilfsfunktionen
M. Goedicke – Programmierung WiSe 2020/2021
11
Lambda in Java: Syntax
▪ Syntax der Lambda-Ausdrücke orientiert sich am Lambda-Kalkül ▪ Kommaseparierte Liste der Parameter
▪ Typdefinitionen nicht notwendig
▪ bei mehreren Parametern müssen diese geklammert werden ▪ Der Pfeil-Operator ->
▪ Funktionskörper
▪ entweder einzelner Ausdruck oder Anweisung(en), durch { } in einem Block zusammengefasst
▪ einzelner Ausdruck wird ausgewertet und Ergebnis zurückgegeben – kein explizites return-Statement notwendig!
▪ Beispiele
▪ (String s) -> { System.out.println(s); } ▪ x -> x * x
▪ (intx,inty)->x+y
▪ (String s) -> { System.out.println(s);
return s.toLowerCase(); } ▪ () -> “It’s a kind of magic.”
▪ (Strings,intx,inty)->s+”(“+x+”|”+y+ “)”
M. Goedicke – Programmierung WiSe 2020/2021
12
Beispiel: Liste sortieren
List
list.add(4); list.add(19); list.add(3); list.add(0); list.add(1);
// Anonyme Klasse
Collections.sort(list, new Comparator
return x – y; }
});
// Lambda-Ausdruck – Typdefinition und return nicht notwendig
Collections.sort(list, (x, y) -> x – y);
M. Goedicke – Programmierung WiSe 2020/2021
13
Beispiel: SAM-Interface spontan implementieren
▪ Spontane Implementierung eines Interfaces mit einer Methode über Lambdas möglich (wie anonyme Klasse):
interface SophisticatedMath { int calculate(int x, int y);
}
class SophisticatedMathTest {
public static void main(String[] args) {
SophisticatedMath myMath = (a, b) -> a * b; myMath.calculate(2, 3); // Ergebnis: 6
SophisticatedMath yourMath = (a, b) -> a + b;
yourMath.calculate(2, 3); // Ergebnis: 5 }
}
M. Goedicke – Programmierung WiSe 2020/2021
14
Type Inference
▪ Java-Compiler ist in der Lage, den Typ von Lambda-Ausdrücken und deren Parametern aus dem Kontext abzuleiten (type inference)
▪ wie in den vorigen Beispielen ist es daher häufig nicht notwendig, explizit Typen anzugeben
▪ Aus einem Lambda-Ausdruck kann je nach Kontext ein unterschiedlicher Typ abgeleitet werden
interface SophisticatedMath {
int calculate(int x, int y); }
interface StringOperation {
String apply(String x, String y); }
// Im Programm:
SophisticatedMath yourMath = (a, b) -> a + b; yourMath.calculate(2, 3); // Ergebnis: 5
StringOperation op = (a, b) -> a + b;
op.apply(“Hallo, “, “Welt!”); // Ergebnis: “Hallo, Welt!”
M. Goedicke – Programmierung WiSe 2020/2021
15
@FunctionalInterface
▪ Bereits angesprochene SAM-Interfaces (Single Abstract Method) sind in Java auch als Functional Interfaces bekannt
▪ Instanzen der funktionalen Interfaces können durch Lambda- Ausdrücke oder Methodenreferenzen erzeugt werden
▪ bspw. kann Compiler (int x) -> x * x als eine Instanz von Function
▪ Java-Compiler behandelt alle Interfaces, welche die SAM-Bedingung erfüllen, als funktionales Interface
▪ dies ermöglichte u.a. das Beispiel von vorheriger Folie: SophisticatedMath yourMath = (a, b) -> a + b;
▪ Programmierer kann Interfaces auch explizit mit der @FunctionalInterface-Annotation als funktional markieren
▪ dadurch stellt Compiler sicher, dass das Interface tatsächlich die Anforderungen an funktionales Interface erfüllt
M. Goedicke – Programmierung WiSe 2020/2021
16
@FunctionalInterface: Beispiel
▪ Beispiel: Versehentlich zwei Methoden in Interface definiert ▪ Ohne @FunctionalInterface
▪ Compiler erkennt nur Fehler bei Zuweisung, weil Lambda- Ausdruck nicht in SophisticatedMath umgewandelt werden kann (kein funktionales Interface)
▪ Mit @FunctionalInterface
▪ Compiler erkennt, dass das
Interface zu viele Methoden enthält
M. Goedicke – Programmierung WiSe 2020/2021
17
Erweiterung der Collections-API
▪ Java 8 erweitert Collections-Framework der Standardbibliothek
▪ Erweiterung speziell für Lambda-Ausdrücke
▪ Vereinfachung des Codes zur Verwaltung von Collections (Listen, Maps,
Sets, etc.)
▪ Vereinfachte Anwendung von Nebenläufigkeit
▪ Interessanteste Erweiterungen:
▪ forEach(Consumer super T> action) ▪ Streams
▪ Collections können über Collection
▪ über Collection
▪ viele Methoden des Interface Stream
M. Goedicke – Programmierung WiSe 2020/2021
18
Erweiterung der Collections-API
▪ Iterable
▪ Beispiel: alle Elemente auf Konsole ausgeben:
List
list.forEach(s -> System.out.println(s));
▪ Stream
▪ erzeugt einen Strom von Elementen vom Typ T
▪ auf diesem Strom sind diverse Operationen verfügbar, deren Parameter
funktionale Interfaces sind
▪ Parameterisierung über Lambda-Ausdrücke
▪ Stream
▪ gibt einen neuen Stream zurück, der nur Elemente enthält, die predicate
genügen
▪ Beispiel: Liste von Dateinamen nach Namen mit Endung “.java” filtern:
list.stream().filter(s -> s.endsWith(“.java”));
M. Goedicke – Programmierung WiSe 2020/2021
19
Erweiterung der Collections-API
▪ Stream
▪ Wendet Funktion mapper auf alle Elemente des Streams an
▪ Achtung: Methode verändert die Elemente nur innerhalb des Streams!
▪ Beispiel: alle Elemente einer String-Liste in Großschreibung umwandeln
(die Liste bleibt unverändert):
list.stream().map(s -> s.toUpperCase()); ▪ T Stream
BinaryOperator
▪ Wendet accumulator auf Wertepaare in Stream an:
▪ zunächst Anwendung von accumulator auf identity und ersten Wert
▪ danach Anwendung von accumulator auf zweiten und dritten Wert
▪ usw.
▪ identity ist sozusagen quasi “Startwert”
▪ Beispiel: Summe der Werte einer List
intList.stream().reduce(0, (a, b) -> a + b);
M. Goedicke – Programmierung WiSe 2020/2021
20
Erweiterung der Collections-API
▪ Die Methoden des Stream
▪ Möglichkeit zur Speicherung eines Streams notwendig
▪ Methode Stream
collector)
▪ “sammelt” alle Objekte des Streams auf
▪ speichert diese nach Anweisung des collector-Ausdrucks
▪ Klasse Collectors bietet vorgefertigte Kollektoren an, u.a. ▪ Collectors.toList() – als List
▪ Collectors.toSet() – als Set
▪ Beispiel (zur Verdeutlichung Bestandteile eingefärbt) List
list.stream().map(s -> s.toUpperCase()).collect(Collectors.toList());
M. Goedicke – Programmierung WiSe 2020/2021
21
Beispiel: Listen filtern
▪ Java 7: foreach-Schleife
List
// … add some books here
List
if(book.getTitle().contains(“Software”) && book.getYearPublished > 2000) {
relevantBooks.add(book);
} }
▪ Java 8: Lambda-Ausdruck
List
// … add some books here
List
book.getTitle().contains(“Software”) && book.getYearPublished > 2000).collect(Collectors.toList());
M. Goedicke – Programmierung WiSe 2020/2021
22
Beispiel: Bibliothek
▪ Java 7: Für jedes Kriterium eine Zugriffsmethode class Library {
private List
public
public
public
public
public
// …
// etwas überspitzt
public List
// … }
List
List
List
M. Goedicke – Programmierung WiSe 2020/2021
23
Beispiel: Bibliothek
▪ Java 8: Konfigurierbare getBooks()-Methode class Library {
private List
public List
return books.stream().filter(predicate).collect(Collectors.toList());
} }
Library library = new Library();
// … Einfügen von Büchern in Library …
// Abfragen
List
!book.getTitle().equals(“The Bible”) && book.getYearPublished() > 2000 && !book.getAuthor().equals(“John Doe”);
M. Goedicke – Programmierung WiSe 2020/2021
24
Fazit: Bibliothek
▪ Der Ansatz über Lambdas ist sehr kurz
▪ Der Programmierer der Library-Klasse muss nicht mehr
vorhersehen, welche Art von Büchern gesucht wird
▪ bietet tatsächlich nur noch die Schnittstelle zum Suchen an
▪ Anwender der Library-Klasse kann beliebige Abfragen starten
▪ Library-Klasse muss nicht verändert werden, wenn sich Book-Klasse
ändert oder Anwender andersartige Bücher suchen möchte ▪ idealer Ansatz für Programm-Bibliotheken (APIs)
M. Goedicke – Programmierung WiSe 2020/2021
25
Methodenreferenzen
▪ Auch bestehende Methoden können wie Lambda-Audrücke behandelt und z.B. an Methoden übergeben werden
▪ Dazu führt Java 8 einen neuen Operator :: ein, über den Methoden referenziert werden
▪ Beispiel
double value = 0.0;
Function
▪ Methodenreferenzen können verwendet werden, um elegant Funktion
auf Listenelemente durchzuführen:
List
▪ Eine besondere Referenz ist die Konstruktor-Referenz ::new
▪ Beispiel
Supplier
Elegantes Beispiel zum Abschluss
▪ Ausgabe des Quadrates all derjenigen ganzen Zahlen zwischen 1 und 20, welche sich durch 2 teilen lassen
IntStream.rangeClosed(1,20)
.filter(i -> i % 2 == 0)
.map(i -> i * i)
.forEach(System.out::println);
M. Goedicke – Programmierung WiSe 2020/2021
27