HOWTO · Java
Der Pfeiloperator -> in Java
In diesem Beitrag geht es darum, den Pfeiloperator in Java zu kennen.
Auf dieser Seite
In Java ist -> das Trennzeichen eines Lambda-Ausdrucks und kein allgemeiner Operator für Berechnungen. Links davon stehen die Parameter des Lambda-Ausdrucks, rechts davon sein Rumpf. Ein Lambda-Ausdruck stellt eine Implementierung für ein kompatibles funktionales Interface bereit, also für ein Interface mit genau einer abstrakten Methode. Lambda-Ausdrücke wurden mit Java 8 eingeführt und können manche Implementierungen verkürzen. Sie ersetzen jedoch nicht jede anonyme Klasse und garantieren weder schnelleren noch besser lesbaren Code.
Bedeutung des Pfeiloperators in Java
Vor Java 8 ließ sich eine Aufgabe für Runnable als anonyme Klasse mit einer run-Methode schreiben. Ein Lambda-Ausdruck beschreibt dasselbe Verhalten so: () -> System.out.println("Working"). Der Pfeil gehört weiterhin zu einem Lambda-Ausdruck; er erzeugt keinen eigenständigen Funktionswert ohne Zieltyp. Der Compiler ermittelt das funktionale Interface anhand des Kontexts, etwa einer Variablendeklaration oder eines Methodenarguments. Die Zeichenkette Working ist der Text, den dieses Beispiel ausgibt.
Syntax und Regeln von Lambda-Ausdrücken
Ein Lambda-Ausdruck besteht aus einer Parameterliste, dem Pfeil und einem Rumpf. Bei einem Ausdruck als Rumpf können die geschweiften Klammern entfallen, wie in x -> x * x; ein Anweisungsblock verwendet geschweifte Klammern und benötigt ein ausdrückliches return, wenn die Zielmethode einen Wert zurückgibt. Ein Lambda-Ausdruck ohne Parameter verwendet (). Mehrere Parameter stehen in Klammern, wie in (left, right) -> left + right. Auch bei einem einzelnen Parameter sind Klammern zulässig, zum Beispiel (name) -> name.length().
Bei einem Lambda-Ausdruck mit Anweisungsblock und Rückgabewert bestimmt der Rückgabetyp der Zielmethode den Ergebnistyp des Lambda-Ausdrucks. Das folgende Beispiel weist das Lambda ToIntFunction<String> zu, dessen Methode applyAsInt einen int zurückgibt:
Der Code und die angezeigten Bezeichnungen oder Ausgaben in diesem Abschnitt bleiben auf Englisch.
import java.util.function.ToIntFunction;
public class BlockLambdaExample {
public static void main(String[] args) {
ToIntFunction<String> length = (String name) -> {
return name.length();
};
System.out.println(length.applyAsInt("Mira"));
}
}
Der Rumpf muss zum Rückgabetyp und zu den Regeln für geprüfte Ausnahmen der Zielmethode passen. Ein Lambda-Ausdruck kann weder einer beliebigen Klasse noch einem Interface mit mehreren voneinander unabhängigen abstrakten Methoden zugewiesen werden. Standard- und statische Interface-Methoden zählen für diese Regel nicht als zusätzliche abstrakte Methoden.
Lambda-Ausdrücke mit funktionalen Interfaces verwenden
Ein Lambda-Ausdruck kann einem kleinen funktionalen Interface zugewiesen und über dessen Methode aufgerufen werden. Dieses eigenständige Beispiel stellt die Klasse mit main an den Anfang, sodass sie direkt gestartet werden kann. draw() gibt die Zeile Drawing a circle aus; dieser Text bleibt im unverändert übernommenen Ausgabeblock auf Englisch.
Führen Sie dieses Beispiel aus:
Der Code und die angezeigten Bezeichnungen oder Ausgaben in diesem Abschnitt bleiben auf Englisch.
public class ArrowExample {
@FunctionalInterface
interface Drawable {
void draw();
}
public static void main(String[] args) {
Drawable shape = () -> System.out.println("Drawing a circle");
shape.draw();
}
}
Drawing a circle
Häufige Anwendungen in Java-Collections, Streams und Threads
Collection-Methoden wie forEach akzeptieren ein funktionales Interface. Ein Lambda-Ausdruck kann daher festlegen, was mit jedem Element geschieht. In diesem Beispiel ist name das aktuelle Listenelement; das Lambda für Consumer<String> gibt es aus:
Der Code und die angezeigten Bezeichnungen oder Ausgaben in diesem Abschnitt bleiben auf Englisch.
import java.util.Arrays;
import java.util.List;
public class CollectionLambdaExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Ava", "Mira");
names.forEach(name -> System.out.println(name));
}
}
Die filter-Methode eines Streams verwendet ein Lambda für Predicate<String>, um passende Elemente auszuwählen. Der anschließende Aufruf von forEach gibt jeden Namen aus, der das Prädikat erfüllt:
import java.util.Arrays;
import java.util.List;
public class StreamLambdaExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Ava", "Mira", "Bo");
names.stream()
.filter(name -> name.length() > 2)
.forEach(name -> System.out.println(name));
}
}
Diese APIs bieten unterschiedliche Möglichkeiten, Iteration und Verarbeitung auszudrücken. Eine herkömmliche Schleife kann jedoch besser geeignet sein, wenn sich damit der Kontrollfluss leichter nachvollziehen lässt.
Für die Aufgabe eines Threads wird häufig Runnable verwendet, da seine einzige abstrakte Methode keine Argumente annimmt und keinen Wert zurückgibt. Dieses Lambda stellt die Runnable-Aufgabe bereit. Das Erzeugen des Thread führt sie noch nicht aus; start() stößt ihre unabhängige Ausführung an:
public class ThreadLambdaExample {
public static void main(String[] args) {
Runnable task = () -> System.out.println("Task running");
Thread worker = new Thread(task);
worker.start();
}
}
Die Ausgabe des Threads kann im Verhältnis zu anderer gleichzeitiger Arbeit in einer anderen Reihenfolge erscheinen; eine bestimmte Ausführungsreihenfolge ist nicht garantiert. Die Zeichenkette Task running bleibt im unverändert übernommenen Codebeispiel auf Englisch. Verwenden Sie Lambda-Ausdrücke, wenn das Ziel-Interface und der umgebende Code das Verhalten verständlich machen. Sie verkürzen die Syntax, ersetzen aber nicht das Verständnis der API und des Nebenläufigkeitsverhaltens.