Die Übergabe von Programmcode in Methoden wurde in Java 8 mithilfe von sogenannten Lambda-Ausdrücken (Lambda-Expressions) vereinfacht. In der konventionellen Java-Sprache werden Lambda Ausdrücke angelehnt an anonyme Java-Klassen als eine Art von »anonymen Methoden« beschrieben. Diese besitzen jedoch eine viel kompaktere Syntax, die daraus resultiert, dass auf Namen, Modifikatoren, Rückgabetyp, throws-Klausel und in vielen Fällen auch auf Parameter verzichtet werden kann. Zwischen Lambdas und anonymen Klassen gibt es viele Parallelen, aber auch Unterschiede.
Syntax und Deklaration von Lambda-Ausdrücken
Konzeptionell gesehen sind Lambda-Ausdrücke wie auch anonyme Klassen Funktionen, die den Methoden aus prozeduralen und objektorientierten Sprachen ähnlich »ein Stück Funktionalität definieren«. Funktionen, ein Prinzip von funktionalen Programmiersprachen, machen aus Java eine objektorientierte Sprache mit funktionalen Elementen.
Im Gegensatz zu Methoden, die ihre Argumente (in diesem Fall Felder von Klassen) ändern können, Ausnahmen auslösen und andere Seiteneffekte produzieren, können Funktionen die übergebenen Daten nicht abändern und erzeugen ein Ergebnis in Form von neuen Daten. Alternativ betrachtet: Methoden beschreiben, wie bestimmte Dinge zu tun sind, und Funktionen, was erledigt werden muss.
Die Reihenfolge der Aufrufe von Funktionen ist nicht relevant (deklarativ) im Gegensatz zur Reihenfolge der Aufrufe von Methoden (imperativ). Methoden bestehen aus »code«, der aufgerufen und ausgeführt wird, während Funktionen in der Literatur auch als »code as data« bezeichnet werden, weil sie nicht nur ausführbar sind, sondern auch wie Daten übergeben werden. In Programmen werden Lambda-Ausdrücke mittels eines Java-Code-Blocks repräsentiert und mittels Typinferenz in funktionale Interfaces konvertiert. Streng genommen sind Lambda-Ausdrücke Instanzen vom Typ eines funktionalen Interface. Sowohl die Typdefinition als auch die Instanziierung erfolgen implizit während der Laufzeit.
Laut Literatur bereitet der Compiler nur die Definition eines synthetischen Typs vor. Sowohl seine Erzeugung als auch die des Objekts, das den Lambda-Ausdruck während der Laufzeit repräsentiert, werden dynamisch vom Laufzeitsystem mittels eines invokedynamic-Befehls durchgeführt. Wenn anonyme Klassen zum Einsatz kommen, wird explizit ein Objekt (vom Typ der Klasse, die dem Schlüsselwort new folgt, bzw. vom Typ Object, wenn der Name eines Interface angegeben wird) in einer Methode übergeben. Wie alle Objekte in Java werden auch diese während der Laufzeit erzeugt. Diese Aktion beinhaltet das Laden einer Klasse, das Allozieren von Speicher für ein Objekt und dessen Initialisierung sowie den Aufruf seiner Methode.
Für Lambda-Ausdrücke ist das explizite Erzeugen von Objekten nicht mehr erforderlich, es wird einfach der Ausdruck selbst, mit anderen Worten der »Code als Daten«, übergeben. Dies ist dahin gehend eine Optimierung, als dass bei einer Definition eines Lambda-Ausdrucks, der nicht in einem Methodenaufruf benutzt wird, weder der synthetische Typ noch das Lambda-Objekt vom Laufzeitsystem erzeugt werden muss.
Die Typinferenz, durch die die Konvertierung in ein funktionales Interface erfolgt, ist auch diesmal eine reine Angelegenheit des Compilers. Der so ermittelte Typ wird als Target-Typ (Zieltyp) bezeichnet. Der vorher erwähnte synthetische Typ (der vom Laufzeitsystem erzeugt wird) ist ein Subtyp dieses Typs.
Lambda-Expressions können nur dann in Programmen verwendet werden, wenn der Target-Typ aus dem umgebenden Kontext ermittelt werden kann. Man kann sie an folgenden Stellen einsetzen: Feld- und Variablen-Deklarationen, Zuweisungen, bedingte Ausdrücke mit ?, Konstruktor- und Methodenargumente, Rückgabe-Statements, der Rumpf von Lambda-Ausdrücken selbst und Cast-Ausdrücke. Die Syntax von Lambda-Ausdrücken ‘(‘Parameter’)’ -> ‘{‘ Statements; ‘}’ wurde aus C# übernommen (mit einem »=>« in »->« abgeänderten Zeichen) und ist der Syntax von Scala-Closures ähnlich, wie die nachfolgenden Beispiele zeigen:

Der Rumpf einer Lambda-Expression kann einen Ausdruck oder einen Block von Statements beinhalten und einen Wert zurückgeben (»value compatible«) oder auch nicht (»voidcompatible«). Eine Mischung aus void- und Wert-kompatibel ist nicht erlaubt.
Die Parameterliste kann deklarierte oder inferierte (»inferred«) Typen enthalten, wobei eine Mischung von beiden ebenfalls nicht erlaubt ist. Besteht die Parameterliste aus einem einzelnen Parameter, dessen Typ inferiert werden kann, können die runden Klammern wegfallen. Wenn der Typ der Parameter vom Compiler nicht inferiert werden kann, muss dieser explizit spezifiziert werden. Wenn der Target- oder Rückgabetyp nicht inferiert werden kann, kann ein Cast dabei helfen:

HalloJava und RechenOperation bezeichnen in diesen Beispielen Namen von benutzerdefinierten funktionalen Interfaces:

bzw.


Dieser Artikel ist ein Auszug aus dem Buch „Java Übungsbuch“ von Elizabeth Jung. Alle Infos zum Buch, das Inhaltsverzeichnis und eine kostenlose Leseprobe findet ihr bei uns im Shop.