Rheinwerk Computing < openbook >


 
Inhaltsverzeichnis
Materialien
Vorwort
1 Java ist auch eine Sprache
2 Imperative Sprachkonzepte
3 Klassen und Objekte
4 Arrays und ihre Anwendungen
5 Der Umgang mit Zeichenketten
6 Eigene Klassen schreiben
7 Objektorientierte Beziehungsfragen
8 Ausnahmen müssen sein
9 Geschachtelte Typen
10 Besondere Typen der Java SE
11 Generics<T>
12 Lambda-Ausdrücke und funktionale Programmierung
13 Architektur, Design und angewandte Objektorientierung
14 Java Platform Module System
15 Die Klassenbibliothek
16 Einführung in die nebenläufige Programmierung
17 Einführung in Datenstrukturen und Algorithmen
18 Einführung in grafische Oberflächen
19 Einführung in Dateien und Datenströme
20 Einführung ins Datenbankmanagement mit JDBC
21 Bits und Bytes, Mathematisches und Geld
22 Testen mit JUnit
23 Die Werkzeuge des JDK
A Java SE-Module und Paketübersicht
Stichwortverzeichnis


Download:

- Listings, ca. 2,7 MB


Buch bestellen
Ihre Meinung?



Spacer
<< zurück
Java ist auch eine Insel von Christian Ullenboom

Einführung, Ausbildung, Praxis
Buch: Java ist auch eine Insel


Java ist auch eine Insel

Pfeil12 Lambda-Ausdrücke und funktionale Programmierung
Pfeil12.1 Funktionale Schnittstellen und Lambda-Ausdrücke
Pfeil12.1.1 Klassen implementieren Schnittstellen
Pfeil12.1.2 Lambda-Ausdrücke implementieren Schnittstellen
Pfeil12.1.3 Funktionale Schnittstellen
Pfeil12.1.4 Der Typ eines Lambda-Ausdrucks ergibt sich durch den Zieltyp
Pfeil12.1.5 Annotation @FunctionalInterface
Pfeil12.1.6 Syntax für Lambda-Ausdrücke
Pfeil12.1.7 Die Umgebung der Lambda-Ausdrücke und Variablenzugriffe
Pfeil12.1.8 Ausnahmen in Lambda-Ausdrücken
Pfeil12.1.9 Klassen mit einer abstrakten Methode als funktionale Schnittstelle? *
Pfeil12.2 Methodenreferenz
Pfeil12.2.1 Motivation
Pfeil12.2.2 Methodenreferenzen mit ::
Pfeil12.2.3 Varianten von Methodenreferenzen
Pfeil12.3 Konstruktorreferenz
Pfeil12.3.1 Parameterlose und parametrisierte Konstruktoren
Pfeil12.3.2 Nützliche vordefinierte Schnittstellen für Konstruktorreferenzen
Pfeil12.4 Funktionale Programmierung
Pfeil12.4.1 Code = Daten
Pfeil12.4.2 Programmierparadigmen: imperativ oder deklarativ
Pfeil12.4.3 Das Wesen der funktionalen Programmierung
Pfeil12.4.4 Funktionale Programmierung und funktionale Programmiersprachen
Pfeil12.4.5 Funktionen höherer Ordnung am Beispiel von Comparator
Pfeil12.4.6 Lambda-Ausdrücke als Abbildungen bzw. Funktionen betrachten
Pfeil12.5 Funktionale Schnittstellen aus dem java.util.function-Paket
Pfeil12.5.1 Blöcke mit Code und die funktionale Schnittstelle Consumer
Pfeil12.5.2 Supplier
Pfeil12.5.3 Prädikate und java.util.function.Predicate
Pfeil12.5.4 Funktionen über die funktionale Schnittstelle java.util.function.Function
Pfeil12.5.5 Ein bisschen Bi …
Pfeil12.5.6 Funktionale Schnittstellen mit Primitiven
Pfeil12.6 Optional ist keine Nullnummer
Pfeil12.6.1 Einsatz von null
Pfeil12.6.2 Der Optional-Typ
Pfeil12.6.3 Primitive optionale Typen
Pfeil12.6.4 Erst mal funktional mit Optional
Pfeil12.6.5 Primitiv-Optionales mit speziellen OptionalXXX-Klassen
Pfeil12.7 Was ist jetzt so funktional?
Pfeil12.8 Zum Weiterlesen
 

Zum Seitenanfang

12.4    Funktionale Programmierung Zur vorigen ÜberschriftZur nächsten Überschrift

 

Zum Seitenanfang

12.4.1    Code = Daten Zur vorigen ÜberschriftZur nächsten Überschrift

Wer den Begriff Daten hört, denkt zunächst einmal an Zahlen, Bytes, Zeichenketten oder auch an komplexe Objekte mit ihrem Zustand. Wir wollen in diesem Kapitel diese Sicht ein wenig erweitern und auf Programmcode lenken. Java-Code, versinnbildlicht als Serie von Bytecodes, besteht auch aus Daten. Und wenn wir uns einmal auf diese Sichtweise einlassen, dass Code gleich Daten ist, dann lässt sich Code auch wie Daten übergeben und so von einem Punkt zum anderen übertragen, speichern und später referenzieren. Mit dieser Möglichkeit, Code zu übertragen, lässt sich das Verhalten von Algorithmen leicht anpassen. Beginnen wir mit ein paar Beispielen, bei denen Programmcode übergeben wird, auf den dann später zugegriffen wird:

  • Ein Thread führt Programmcode im Hintergrund aus. Der Programmcode, den der Java-Thread ausführen soll, wird in ein Objekt vom Typ Runnable verpackt, genau genommen in eine run()-Methode gesetzt. Kommt der Thread zum Zuge, ruft er die run()-Methode auf.

  • Ein Timer ist eine java.util-Klasse, die zu bestimmten Zeitpunkten Programmcode ausführen kann. Der Objektmethode scheduleAtFixedRate(…) wird dabei ein Objekt vom Typ TimerTask übergeben, das den Programmcode enthält.

  • Zum Sortieren von Daten kann eine eigene Ordnung definiert werden, die dem Sortierer als Comparator übergeben werden kann. Der Comparator deklariert eine Vergleichsmethode, an die sich der Sortierer wendet, um zwei Objekte in die gewünschte Reihenfolge zu bringen.

  • Aktiviert der Benutzer auf der Oberfläche eine Schaltfläche, so führt das zu einer Aktion. Der Programmcode steckt – zum Beispiel beim UI-Framework Swing – in einem Objekt vom Typ ActionListener und wird an der Schaltfläche JButton mit addActionListener(…) festgemacht. Kommt es zu einer Schaltflächenaktivierung, arbeitet das UI-System den Programmcode in der Methode actionPerformed(…) des gespeicherten ActionListener ab.

Um Programmcode von einer Stelle zur anderen zu bringen, wird in Java immer der gleiche Mechanismus eingesetzt: Eine Klasse implementiert eine (in der Regel nichtstatische) Methode, in der der auszuführende Programmcode steht. Ein Objekt dieser Klasse wird an eine andere Stelle übergeben, und der Interessent greift dann über die Methode auf den Programmcode zu. Dass ein Objekt noch mehr als diese eine Implementierung enthalten kann, etwa Variablen, Konstanten und Konstruktoren, ist dafür nicht relevant. Diesen Mechanismus schauen wir uns jetzt in verschiedenen Varianten genauer an.

 

Zum Seitenanfang

12.4.2    Programmierparadigmen: imperativ oder deklarativ Zur vorigen ÜberschriftZur nächsten Überschrift

In irgendeiner Weise muss ein Entwickler sein Problem in Programmform beschreiben, damit der Computer es letztendlich ausführen kann. Hier gibt es verschiedene Beschreibungsformen, die wir Programmierparadigmen nennen. Bisher haben wir uns immer mit der imperativen Programmierung beschäftigt, bei der Anweisungen im Mittelpunkt stehen. Wir haben im Deutschen den Imperativ, also die Befehlsform, die sehr gut mit dem Programmierstil vergleichbar ist, denn es handelt sich in beiden Fällen um Anweisungen der Art »tue dies, tue das«. Diese »Befehle« mit Variablen, Fallunterscheidungen und Sprüngen beschreiben das Programm und den Lösungsweg.

Zwar ist imperative Programmierung die technisch älteste, aber nicht die einzige Form, Programme zu beschreiben. Daneben gibt es die deklarative Programmierung, die nicht das Wie zur Problemlösung beschreibt, sondern das Was – also was eigentlich gefordert ist, ohne sich in genauen Abläufen zu verstricken. Auf den ersten Blick klingt das abstrakt, aber jeder, der schon einmal

  • eine Selektion wie »*.html« auf der Kommandozeile bzw. im Explorer-Suchfeld getätigt,

  • eine Datenbankabfrage mit SQL geschrieben,

  • eine XML-Selektion mit XQuery genutzt,

  • ein Build-Skript mit Ant oder make formuliert oder

  • eine XML-Transformation mit XSLT beschrieben hat,

wird das Prinzip kennen.

Bleiben wir kurz bei SQL, um einen Punkt deutlich zu machen. Natürlich führt im Endeffekt die CPU die Abarbeitung der Tabellen und die Auswertungen der Ergebnisse rein imperativ aus, doch es geht um die Programmbeschreibung auf einem höheren Abstraktionsniveau. Deklarative Programme sind üblicherweise wesentlich kürzer, und damit kommen weitere Vorteile wie leichtere Erweiterbarkeit und Verständlichkeit ins Spiel. Da deklarative Programme oftmals einen mathematischen Hintergrund haben, lassen sich die Beschreibungen leichter formal in ihrer Korrektheit beweisen.

Deklarative Programmierung ist ein Programmierstil, und eine deklarative Beschreibung braucht eine Art »Ablaufumgebung«, denn SQL kann zum Beispiel keine CPU direkt steuern. Aber anstatt nur spezielle Anwendungsfälle wie Datenbank- oder XML-Abfragen zu behandeln, können auch typische Algorithmen deklarativ formuliert werden, und zwar mit funktionaler Programmierung. Damit sind imperative Programme und funktionale Programme gleich mächtig in ihren Möglichkeiten.

 

Zum Seitenanfang

12.4.3    Das Wesen der funktionalen Programmierung Zur vorigen ÜberschriftZur nächsten Überschrift

Während bei der imperativen Programmierung die Abfolge von Anweisungen im Mittelpunkt steht und bei objektorientierten Programmen Objekte mit Zustand und Verhalten, ist die funktionale Programmierung durch andere Dinge gekennzeichnet.

Pure Funktionen ohne Seiteneffekte

In der Mathematik ist eine Funktion eine Abbildung von Elementen einer Definitionsmenge auf genau ein Element einer Zielmenge. double zero = Math.sin(Math.PI); wäre so ein Beispiel, String HEINZUNDEVA = "heinzUndEva".toUpperCase() ein weiteres aus objektorientierter Sicht: Ein Wert kommt rein und ein Wert kommt raus.

Wir sprechen von puren Funktionen, wenn sie

  • bei gleichen Eingaben immer die gleichen Ausgaben liefern und

  • keine Seiteneffekte haben, also keinen äußeren Zustand modifizieren.

Folglich ist Math.sin(…) eine pure Funktion, System.out.println() oder Math.random() sind es nicht. Dabei spielt es keine Rolle, dass die Implementierungen durchaus Zustände, etwa auf dem Stack, verändern, und somit Speicherzellen beschreiben – wichtig ist die konzeptionelle Sicht. Die API-Dokumentation sollte so gut sein, dass sie Seiteneffekte benennt.

Einen aus puren Funktionen aufgebauten Ausdruck nennen wir puren Ausdruck. Er hat eine Eigenschaft, die sich in der Informatik referenzielle Transparenz nennt, dass nämlich das Ergebnis eines Ausdrucks anstelle des Ausdrucks selbst gesetzt werden kann, ohne dass das Programm ein anderes Verhalten zeigt. Statt Math.sin(Math.PI) kann jederzeit 0 gesetzt werden; das Ergebnis wäre das gleiche. Ein Compiler kann bei referenzieller Transparenz diverse Optimierungen durchführen.

In der objektorientierten Programmierung modifizieren wir über Methoden die Zustände von Objekten. Ein Radio soll lauter spielen? Wir rufen volumeUp() auf, und der interne Zustand volume wird erhöhlt. Einen Zustand in Objekten zu haben, ist für objektorientierte Softwarentwicklung selbstverständlich.

Objektorientierte Entwickler bekommen ein komisches Gefühl, wenn sie »funktionale Programmierung« hören, denn es klingt so, als ob es keine Zustände mehr gibt. Doch dies ist ein Irrglaube. Es gibt immer noch Zustände, nur sind diese »woanders«. Dazu betrachten wir noch einmal "heinzUndEva".toUpperCase(). Was ist hier der Zustand? Modifiziert toUpperCase() einen Zustand? Die String-Methode verändert keinen Zustand von einem existierenden String-Objekt. Das Wesentliche ist, dass die Rückgabe von toUpperCase() ein neues String-Objekt mit den gewünschten Zuständen ist. Das ist der Kern der funktionalen Programmierung: Funktionen bekommen ein Objekt und liefern ein Objekt auch wieder zurück, nur die Objekte werden nicht verändert und können (und sollten sogar) immutable sein. Fassen wir zusammen: In der Objektorientierung führt eine Operation mit Seiteneffekten zu einer Änderung am Objekt; in der funktionalen Programmierung führt eine Operation zu einem neuen Objekt ohne Änderung am alten Objekt.

Funktionale Bibliotheken lassen sich gut daran erkennen, dass sie Objekte verlangen und Objekte zurückliefern. Misstrauisch macht daher jede Methode, die void liefert. Denn hier muss der Zustand irgendwo anders bezogen und gespeichert werden. Dieser Zustand kann beim Schreiben zu einem Problem führen, sodass bei modernen parallelen Zugriffen eine Sicherung nötig ist.

Verschachtelte Aufrufe statt Anweisungsabfolgen

Während imperative Programme zwischen Anweisungen und Ausdrücken unterscheiden, ist bei funktionalen Programmiersprachen alles ein Ausdruck. In der funktionalen Gedankenwelt stehen Auswertungen von Ausdrücken im Fokus und nicht die sequenzielle Abarbeitung von Anweisungen.

Natürlich schreiben Entwickler funktionaler Programme auch nicht alles zigmal verschachtelt in eine Zeile, sondern nutzen mehrere Zeilen. Viele funktionale Programmiersprachen bieten einen Operator, der an eine Variablenzuweisung erinnert, doch ist es vielmehr ein Name, ein Symbol, für den Ausdruck – der Zusammenhang ist wieder referenzielle Transparenz. Das ist etwas ganz anderes als in imperativen Programmiersprachen, wo die Variable für einen Speicherbereich steht. Bei funktionalen Programmiersprachen machen Bezeichner komplexe verschachtelte Ausdrücke nur lesbarer.

In Java sind nur wenige Anweisungen auch Ausdrücke, etwa Methodenaufrufe mit Rückgaben oder Zuweisungen. Fallunterscheidungen und Schleifen geben kein Ergebnis zurück, und das ist ein Grund, warum es beides so in funktionalen Programmiersprachen nicht gibt. Doch wir kennen auch in Java den Bedingungsoperator, und auch mit ihm lässt sich eine Fallunterscheidung mit Alternative realisieren, und dies ist ein Ausdruck. Wiederholungen lassen sich in funktionalen Programmiersprachen über Rekursionen realisieren. Ein typisches Beispiel ist die Berechnung der Fakultät. Die Formel lautet n! = 1 × 2 × 3 × … × n, und mit Schleifen und Variablen, dem imperativen Weg, sieht sie so aus:

public static int factorial( int n ) {

int result = 1;

for ( int i = 1; i <= n; i++ )

result *= i;

return result;

}

Deutlich sind die vielen Zuweisungen und die Fallunterscheidung durch die Schleife abzulesen, die typischen Indikatoren für imperative Programme. Der Schleifenzähler erhöht sich, damit kommt Zustand in das Programm, denn der aktuelle Index muss ja irgendwo im Speicher gehalten werden. Bei der rekursiven Variante ist das ganz anders. Hier gibt es keine Zuweisungen im Programm, und die Schreibweise erinnert an die mathematische Definition:

public static int factorial( int n ) {

return n == 0 ? 1 : n * factorial( n - 1 );

}

Da die Anzahl der Wiederholungen hoch sein kann und bei Rekursionen traditionell der Stack in Anspruch genommen wird, sind Optimierungen so wichtig. Viele Entwickler schauen daher genau auf die Optimierung von endrekursiven Methoden – das sind Methoden, die als Allerletztes die Methode selbst wieder aufrufen; in diesem Fall kann Platz auf dem Stack eingespart werden. Die JVM kann das bisher nicht.

Funktionen erster Ordnung und Funktionen höherer Ordnung

Die beiden bisher genannten Punkte lassen sich mit Disziplin auch in Java umsetzen. Eine Sache gibt es jedoch, die mit Java nicht möglich, aber in funktionalen Programmiersprachen üblich ist: Funktionen haben einen ganz anderen Stellenwert, es sind eigene Typen.

[zB]  Beispiel

Zuweisung von Funktionen in JavaScript und spätere Ausführung

function printVodka() { console.log("Vodka"); }

function printWhiskey() { console.log("Whiskey"); }

printDrink = Math.random() > 0.5 ? printVodka : printWhiskey;

printDrink();

In Java haben wir nur primitive Typen und Referenztypen, aber Methoden als solches sind keine Typen. Funktionen sind in funktionalen Programmiersprachen Werte und können

  • Variablen zugewiesen werden,

  • anderen Funktionen übergeben werden und

  • Funktionen können Funktionen wiederum zurückgeben.

Wir sprechen von Funktionen höherer Ordnung (engl. higher order functions). Das erlaubt ein sehr flexibles Zusammensetzen von Funktionen, denn Funktionen können ganz einfach weitergereichet werden.

 

Zum Seitenanfang

12.4.4    Funktionale Programmierung und funktionale Programmiersprachen Zur vorigen ÜberschriftZur nächsten Überschrift

Mit der funktionalen Programmierung haben wir eine echte Alternative zur imperativen Programmierung. Sprachen, die in dieses Paradigma fallen, sind Lisp (erstmals 1958) und Derivate wie Clojure, Erlang, ML (und OCaml), F# sowie Haskell. Java ist keine funktionale Programmiersprache, da ihr die Möglichkeit fehlt, Funktionen höherer Ordnung einzusetzen, doch mit einigen Tricks lässt sich der Nachteil fast ausgleichen.

Warum funktionale Programmierung einen schweren Stand hat

Ein Blick auf Listen mit populären Programmiersprachen[ 224 ](Etwa http://www.tiobe.com/tiobe-index/ oder http://githut.info. ) zeigt, dass rein funktionale Programmiersprachen dort nicht auftauchen. Es stellt sich die Frage, warum die funktionale Programmierung einen so schweren Stand hat und bei den Entwicklern gefürchtet ist. Das hat mehrere Gründe:

  • Lesbarkeit: Am Anfang der funktionalen Programmiersprachen steht historisch Lisp aus dem Jahr 1958, eine sehr flexible, aber ungewohnt zu lesende Programmiersprache. Unsere Fakultät sieht in Lisp so aus: (defun factorial (n) (if (= n 1) 1 (* n (factorial (- n 1))))). Die ganzen Klammern machen die Programme nicht einfach lesbar, und die Ausdrücke stehen in der Präfix-Notation - n 1 statt in der üblichen Infix-Notation n - 1. Bei anderen funktionalen Programmiersprachen ist es anders, dennoch führt das zu einem gewissen Vorurteil, dass alle funktionalen Programmiersprachen schlecht lesbar seien.

  • Begriffe: Funktionen höherer Ordnung, Idempotenz, Funktion, Funktor, Prädikat, Stelligkeit (Arität), Lambda, Closure, Currying, Monoid und Monad, das sind Begriffe, die glauben machen, funktionale Programmierung sei nur etwas für Mathematiker oder Informatiker – oder für Ärzte nach einer Umschulung. Die Konzepte schrecken viele Entwickler ab, und folglich beschäftigen sie sich nicht mit dem Paradigma.

  • Akademisch und mathematisch: Funktionale Programmierung hat auch deswegen etwas Akademisches, weil dieses Programmierparadigma in den Köpfen der Entwickler oftmals nur mit mathematischen Funktionen in Verbindung gebracht wird. Und die wenigsten werden tatsächlich die Berechnung der Fakultät oder Fibonacci-Zahlen in Programmen benötigen und daher schnell die funktionale Programmierung beiseitelegen. Doch diese Vorurteile sind unbegründet, und es ist hilfreich, die funktionale Programmierung gedanklich von der Mathematik zu lösen, denn die allermeisten Programme haben nichts mit mathematischen Funktionen im eigentlichen Sinne zu tun, wohl aber viel stärker mit formal beschriebenen Methoden.

  • Performance und Speicherverbrauch: Ohne clevere Optimierungen vonseiten des Compilers und der Laufzeitumgebung führen insbesondere rekursive Aufrufe zu prall gefüllten Stacks und schlechter Laufzeit.

  • Rein funktional: Es gibt funktionale Programmiersprachen, die als »rein« oder »pur« bezeichnet werden und keine Zustandsänderungen erlauben. Die Entwicklung von Ein-/Ausgabeoperationen oder simplen Zufallszahlen ist ein großer Akt, der für normale Entwickler nicht mehr nachvollziehbar ist.

Funktional-objektorientierte Sprachen sind beliebt

In den letzten Jahren haben viele objektorientierte Programmiersprachen Unterstützung für funktionales Programmieren bekommen bzw. es sind neue funktionale Programmiersprachen mit pragmatischen Zustandsänderungen erschienen. Aus heutiger Sicht stellt sich eine Kombination aus beiden Konzepten als zukunftsweisend dar. Funktionale Programmierung ist eine Denkweise mit Vorteilen, gerade in der Welt der parallelen Ausführung, wo Zustandsänderungen schnell ein Problem werden können. Außerdem verschiebt die deklarative Programmierung mehr Verantwortung an das Framework, das im Verlauf durch Optimierungen immer besser werden kann.

Wie funktional mit Java programmieren?

Java erlaubt uns nicht, Methoden als Werte zu übergeben – es sind keine Werte und keine Funktionen erster Klasse. Da wir keine Funktionen übergeben können, hilft ein Trick: Anstatt Funktionen zu übergeben, setzen wir

  1. die Funktionalität in eine Methode,

  2. setzen die Methode in eine Klasse,

  3. erzeugen ein Objekt dieser Klasse und

  4. übergeben dieses Objekt als Art Hülle für die Funktion.

Damit der Empfänger nicht irgendein Objekt mit irgendeiner Methode bekommt, kommt eine Schnittstelle als Vermittler dazu: Eine Seite implementiert die Schnittstelle und hält sich an einen Vertrag, der Empfänger bekommt etwas vom gewünschten Typ und kann die Methode aufrufen.

Mit diesem Umweg kann auch Java weit kommen, und die Möglichkeit gibt es prinzipiell seit Java 1.0. Allerdings ist der Codeumfang groß, sodass der Weg unpraktisch war. Doch Lambda-Ausdrücke sind das perfekte Sprachwerkzeug, denn sie machen funktionale Programme kompakt und gut lesbar, und die JVM hat ausgezeichnete Optimierungsmöglichkeiten. Natürlich kann Java niemals das, was eine hundertprozentige funktionale Programmiersprache kann (und erzwingt), doch pragmatisch gesehen reicht das für die Praxis gut aus.

Java ermöglicht objektorientiertes und funktionales Programmieren, und Entwickler können den Weg wählen, der für eine Problemlösung am besten ist. Diese Mehrdeutigkeit schafft natürlich auch Probleme, denn immer, wenn es mehrere Lösungswege gibt, entstehen Auseinandersetzungen um die beste der Varianten – und hier kann von Entwickler zu Entwickler eine konträre Meinung herrschen. Doch ein funktionaler Ansatz hat unbestrittene Vorteile, und das wollen wir uns genau anschauen.

 

Zum Seitenanfang

12.4.5    Funktionen höherer Ordnung am Beispiel von Comparator Zur vorigen ÜberschriftZur nächsten Überschrift

Betrachten wir erneut unser Beispiel aus der Einleitung, die Sortierung von Strings, aber diesmal aus der Sicht eines funktionalen Programmierers. Ein Comparator ist eine einfache »Funktion« mit zwei Parametern und einer Rückgabe. Diese »Funktion« (realisiert als Methode) wiederum wird an die sort(…)-Methode übergeben. Alles das ist funktionale Programmierung, denn wir programmieren Funktionen und übergeben sie. Tabelle 12.10 zeigt drei Beispiele (Generics ausgelassen):

Code

Bedeutung

Comparator c1 = (s, t) ->

Implementiert eine Funktion über einen Lambda-Ausdruck.

Arrays.sort(array, c1);

Nimmt eine »Funktion« als Argument an.

Comparator c2 = Collections.reverseOrder(c1);

Nimmt eine »Funktion« an und liefert auch eine zurück.

Tabelle 12.10    Beispiele für Funktionen in der Übergabe und als Rückgabe

Wir haben schon festgestellt, dass sich in Java keine Funktionen übergeben lassen, also helfen wir uns mit der Möglichkeit, die Funktionalität in eine Methode zu setzen, sodass die Funktion zum Objekt mit einer Methode wird, was die Logik realisiert. Lambda-Ausdrücke bzw. Methoden-/Konstruktorreferenzen sorgen für eine kompakte Syntax ohne den Ballast, extra eine Klasse mit einer Methode schreiben zu müssen.

Der Typ Comparator ist eine funktionale Schnittstelle und steht für eine besondere Funktion mit zwei Parametern gleichen Typs und einer Ganzzahl-Rückgabe. Es gibt weitere funktionale Schnittstellen, die etwas flexibler sind als Comparator, in der Weise, dass etwa die Rückgabe statt int auch double oder etwas anderes sein kann.

 

Zum Seitenanfang

12.4.6    Lambda-Ausdrücke als Abbildungen bzw. Funktionen betrachten Zur vorigen ÜberschriftZur nächsten Überschrift

Wir haben gesehen, dass sich Lambda-Ausdrücke in einer Syntax formulieren lassen, die folgende allgemeine Form hat:

( LambdaParameter ) -> { Anweisungen }

Der Pfeil macht gut deutlich, dass wir es bei Lambda-Ausdrücken mit Funktionen zu tun haben, die etwas abbilden. Im Fall vom Comparator ist es eine Abbildung von zwei Strings auf eine Ganzzahl – in eine etwas mathematischere Notation gepackt: (String, String)  int.

[zB]  Beispiel

Methoden gibt es mit und ohne Rückgabe und mit und ohne Parameter. Genauso ist das mit Lambda-Ausdrücken. Tabelle 12.11 zeigt ein paar Beispiele in Java-Code mit ihren Abbildungen:

Lambda-Ausdruck

Abbildung

(int a, int b) a + b

(int, int) → int

(int a) Math.abs( a )

(int) → int

(String s) s.isEmpty()

(String) → boolean

(Collection c) c.size()

(Collection) → int

() Math.random()

() → double

(String s) { System.out.print( s ); }

(String) → void

() {}

() → void

Tabelle 12.11    Lambda-Ausdrücke und was sie als Funktionen abbilden

[»]  Begriff: Funktion versus Methode

Die Java-Sprachdefinition kennt den Begriff Funktion nicht, sondern spricht nur von Methoden. Methoden hängen immer an Klassen, und das heißt, dass Methoden immer an einem Kontext hängen. Das ist zentral bei der Objektorientierung, da Methoden auf Attribute lesend und schreibend zugreifen können. Lambda-Ausdrücke wiederum realisieren Funktionen, die erst einmal ihre Arbeitswerte rein aus den Parametern beziehen; sie hängen nicht an Klassen und Objekten. Der Gedanke bei funktionalen Programmiersprachen ist ja der, ohne Zustände auszukommen, also Funktionen so clever anzuwenden, dass sie ein Ergebnis liefern. Funktionen geben für eine spezifische Parameterkombination immer dasselbe Ergebnis zurück, unabhängig vom Zustand des umgebenden Gesamtprogramms.

 


Ihre Meinung?

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de

<< zurück
 Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: Java ist auch eine Insel Java ist auch eine Insel

Jetzt Buch bestellen


 Buchempfehlungen
Zum Rheinwerk-Shop: Captain CiaoCiao erobert Java

Captain CiaoCiao erobert Java




Zum Rheinwerk-Shop: Java SE 9 Standard-Bibliothek

Java SE 9 Standard-Bibliothek




Zum Rheinwerk-Shop: Algorithmen in Java

Algorithmen in Java




Zum Rheinwerk-Shop: Objektorientierte Programmierung

Objektorientierte Programmierung




 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und in die Schweiz

InfoInfo



 

 


Copyright © Rheinwerk Verlag GmbH 2021

Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.

Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

 

[Rheinwerk Computing]



Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de



Cookie-Einstellungen ändern