Funktionale vs. Imperative Programmierung 13. Funktionale Konzepte in Java Funktionale Programmierung, Lambda Ausdrücke, Datenströme, Pipelines Imperative Konzepte Ausführen von Anweisungen Zustand (z.b. Felder) Änderbare Datentypen Fokus auf Datenstrukturen Fokus auf das wie Funktionale Konzepte Evaluierung von Ausdrücken Kein Zustand Immutable Datentypen Fokus auf Datenströme Fokus auf das was 325 326 : Einlesen von Daten - Imperativ : Einlesen von Daten - Funktional try (BufferedReader br=new BufferedReader(new FileReader("daten.csv"))){ LinkedList<Messwert> resultat = new LinkedList<>(); br.readline(); String zeile ; while (( zeile = br.readline())!= null){ Messwert m = new Messwert(zeile); resultat. add(m); return resultat ; try (Stream<String> stream = Files.lines(Paths.get("daten.csv"))) { return stream.skip(1). map(messwert::new).collect(tolist()); 327 328
Streams - Datenströme In Java sind Streams die Basis für funktionale Programmierung. Quellen von Datenströmen: Dateien Arrays Datenstrukturen Stream<String> stream = Files.lines (...)) Operationen auf Datenströmen: Map Map: Anwenden von Funktionen auf den einzelnen Elementen des Streams Mathematische Berechnungen Erstellen neuer Objekte basierend auf den existierenden Elementen. map(messwert::new) 329 330 Operationen auf Datenströmen: Reduce : Suchen von Daten - Imperativ Reduce: Aggregierung der einzelnen Elemente eines Datenstroms zu einem einzelnen Wert. Statistische Aggregationen Ablegen der Elemente in Datenstrukturen List<Messwert> daten = lesecsvdaten(); Koordinate ref = lesekoordinate(); for (Messwert m : daten){ if (m.position.nahebei(ref)){ System.out.println(m. originalzeile ); collect (tolist ()) 331 332
: Suchen von Daten - Funktional Operationen auf Datenströmen: Filter Filter: Herausfiltern einzelner Elemente des Streams. List<Messwert> daten = lesecsvdaten(); Koordinate ref = lesekoordinate(); daten.stream(). filter (m > ref.nahebei(m.position)). foreach(system.out::println ); Illegale Werte entfernen Auswahl von Werten basierend auf Anfragen filter (m > ref.nahebei(m.position)) 333 334 Operationen auf Datenströmen: Seiteneffekte Funktionalität als Parameter Seiteneffekte: Der nicht-funktionale Aspekt: Ausführen von beliebigen Operationen aufgrund einzelner Elemente. Input/Ouput Datenstrukturen updaten foreach(system.out::println) Operationen auf dem Stream haben Funktionalität (Code) als Parameter, anstelle von Daten Möglichkeiten, Funktionalität zu übergeben: lose Code-Stücke Referenzen auf Methoden Referenzen auf Konstruktoren Wie können wir dies bewerkstelligen? 335 336
Lambda Ausdrücke Lambda Ausdrücke sind im Wesentlichen Methoden ohne Namen. Normale Methode double diskriminante(double a, double b, double c){ Gleichwertiger Lambda Ausdruck (double a, double b, double c) > { Lambda Ausdrücke Lambda Ausdruck (double a, double b, double c) > { Ohne explizite Typdeklaration der Parameter (a, b, c) > { Mit einem einzelnen Ausdruck statt einem Body (a, b, c) > b b 4 a c 337 338 Lambda Ausdruck im filter (m > ref.nahebei(m.position)) Die Methode filter erwartet eine Methode als Parameter, welche einen Messwert entgegennimmt, und einen boolean zurueckgibt. m ist ein Parameter vom Typ Messwert ref.nahebei(m.position) ist ein einzelner booleanausdruck Die Variable ref aus dem definierenden Kontext ist zugänglich, solange sie effektiv konstant (final) ist. Referenzen auf Methoden Um eine Methode auf einem Objekt auszuführen, schreiben wir: objekt.methode() Um eine Referenz auf eine Methode auf einem Objekt anzugeben ohne diese auszuführen, schreiben wir: objekt::methode 339 340
Referenzen auf statische Methoden Referenz auf eine Methode im Um eine statische Methode auszuführen, schreiben wir: Klasse.methode() foreach(system.out::println) Um eine Referenz auf eine statische Methode anzugeben, schreiben wir: Klasse::methode Die Methode foreach erwartet eine Methode, welche nichts zurück gibt und ein Argument vom Typ Messwert akzeptiert. Die Methode println auf Objekt out erfüllt diese Eigenschaften. 341 342 Referenzen auf Konstruktoren Referenz auf einen Konstruktor im Um einen Konstruktur einer Klasse auszuführen, schreiben wir: new Klasse() map(messwert::new) Um eine Referenz auf einen Konstruktor anzugeben ohne das Objekt dabei zu erstellen, schreiben wir: Klasse::new Die Methode map erwartet eine Methode, welche ein Objekt eines gewissen Datentyps zurueckgibt (egal welcher, solange immer der gleiche) und ein Argument vom Typ String akzeptiert. Der Konstruktor der Klasse Messwert erfüllt diese Eigenschaft. 343 344
Vor- und Nachteile von Funktionaler Programmierung Weniger fehleranfällig Einfacher zu unterhalten Ermöglicht elegante Programmierung Nicht abhängig von spezifischer Architektur Neue Sprachkonzepte zu lernen Ausführungsdetails unbekannt Aufgesetzt auf imperativer Sprache 345