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