CS计算机代考程序代写 algorithm Java scheme ocaml compiler python Erlang Programmierung

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 oder
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 = new LinkedList<>();
list.add(4); list.add(19); list.add(3); list.add(0); list.add(1);
// Anonyme Klasse
Collections.sort(list, new Comparator() { public int compare(Integer x, Integer y) {
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 interpretieren
▪ 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 action) ▪ Streams
▪ Collections können über Collection.stream()-Methode in Stream überführt werden
▪ über Collection.parallelStream() können Operationen ohne zusätzlichen Aufwand parallelisiert werden
▪ viele Methoden des Interface Stream geben wiederum einen Stream zurück – damit ist Verkettung von Aufrufen möglich
M. Goedicke – Programmierung WiSe 2020/2021
18

Erweiterung der Collections-API
▪ Iterable.forEach(Consumer action) ▪ Ausführung von Action auf allen Elementen
▪ Beispiel: alle Elemente auf Konsole ausgeben:
List list = new LinkedList<>(); // …
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 Stream.filter(Predicate predicate)
▪ 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 Stream.map(Function mapper)
▪ 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.reduce(T identity,
BinaryOperator accumulator)
▪ 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 berechnen:
intList.stream().reduce(0, (a, b) -> a + b);
M. Goedicke – Programmierung WiSe 2020/2021
20

Erweiterung der Collections-API
▪ Die Methoden des Stream-Interface verändern nur den Stream, nicht die Quelle des Streams (bspw. einer Liste)
▪ Möglichkeit zur Speicherung eines Streams notwendig
▪ Methode Stream.collect(Collector
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 uppercaseList =
list.stream().map(s -> s.toUpperCase()).collect(Collectors.toList());
M. Goedicke – Programmierung WiSe 2020/2021
21

Beispiel: Listen filtern
▪ Java 7: foreach-Schleife
List books = new ArrayList();
// … add some books here
List relevantBooks = new ArrayList(); foreach(Book book : books) {
if(book.getTitle().contains(“Software”) && book.getYearPublished > 2000) {
relevantBooks.add(book);
} }
▪ Java 8: Lambda-Ausdruck
List books = new ArrayList();
// … add some books here
List relevantBooks = books.stream().filter(book ->
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 books = new ArrayList<>();
public
public
public
public
public
// …
// etwas überspitzt
public List getBooksFromYearAndAuthorWhoseTitleIs(int year, String author, String title) { /* … */ }
// … }
List getBooksWhoseTitleContains(String str) { /* … */ } List getBooksWhoseTitleIs(String title) { /* … */ } List getBooksBefore(int year) { /* … */ }
List getBooksAfter(int year) { /* … */ }
List getBooksFrom(int year) { /* … */ }
M. Goedicke – Programmierung WiSe 2020/2021
23

Beispiel: Bibliothek
▪ Java 8: Konfigurierbare getBooks()-Methode class Library {
private List books = new ArrayList<>();
public List getBooks(Predicate predicate) {
return books.stream().filter(predicate).collect(Collectors.toList());
} }
Library library = new Library();
// … Einfügen von Büchern in Library …
// Abfragen
List b1 = library.getBooks(book -> book.getTitle().contains(“software”)); List b2 = library.getBooks(book -> book.getTitle().equals(“The Bible”)); List b3 = library.getBooks(book -> book.getYearPublished() < 2000); // komplexe Abfrage List myBooks = library.getBooks(book -> book.getTitle().contains(“The”) &&
!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 func = Math::sin; System.out.println(func.apply(value)); // Ausgabe: 0.0 func = Math::cos; System.out.println(func.apply(value)); // Ausgabe: 1.0
▪ Methodenreferenzen können verwendet werden, um elegant Funktion
auf Listenelemente durchzuführen:
List list = new ArrayList<>(); // … Liste füllen … list.forEach(System.out::println);
▪ Eine besondere Referenz ist die Konstruktor-Referenz ::new
▪ Beispiel
Supplier supplier = Object::new; System.out.println(supplier.get()); // java.lang.Object@84aee7
M. Goedicke – Programmierung WiSe 2020/2021
26

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