Verwenden Sie in Java nach Möglichkeit Lambda-Ausdrücke?


52

Ich habe kürzlich den Lambda-Ausdruck beherrscht, der in Java 8 eingeführt wurde. Wenn ich eine Funktionsschnittstelle verwende, verwende ich normalerweise immer einen Lambda-Ausdruck, anstatt eine Klasse zu erstellen, die die Funktionsschnittstelle implementiert.

Wird dies als gute Praxis angesehen? Oder sind ihre Situationen, in denen die Verwendung eines Lambda für eine funktionale Schnittstelle nicht angemessen ist?


122
Auf die Frage "Wendet man Technik X an, wann immer es möglich ist " lautet die einzig richtige Antwort "Nein, wendet Technik X an, wenn immer es möglich, aber sinnvoll ist" .
Doc Brown

Die Wahl, wann ein Lambda (das anonym ist) im Vergleich zu einer anderen Art der funktionalen Implementierung (z. B. einer Methodenreferenz) verwendet werden soll, ist eine wirklich interessante Frage. Ich hatte vor ein paar Tagen die Gelegenheit, Josh Bloch zu treffen, und dies war einer der Punkte, über die er sprach. Soweit ich weiß, plant er, der nächsten Ausgabe von Effective Java einen neuen Artikel hinzuzufügen, der sich genau mit dieser Frage befasst.
Daniel Pryden

@DocBrown Das sollte eine Antwort sein, kein Kommentar.
gnasher729

1
@ gnasher729: definitiv nicht.
Doc Brown

Antworten:


116

Es gibt eine Reihe von Kriterien, anhand derer Sie erwägen sollten, kein Lambda zu verwenden:

  • Größe Je größer ein Lambda wird, desto schwieriger wird es, der Logik zu folgen, die es umgibt.
  • Wiederholung Es ist besser, eine benannte Funktion für wiederholte Logik zu erstellen, obwohl es in Ordnung ist, sehr einfache Lambdas zu wiederholen, die auseinander gespreizt sind.
  • Benennung Wenn Sie sich einen großartigen semantischen Namen vorstellen können, sollten Sie diesen verwenden, da er Ihrem Code viel Klarheit verleiht. Ich spreche keine Namen wie priceIsOver100. x -> x.price > 100ist genauso klar wie dieser Name. Ich meine, solche Namen isEligibleVoterersetzen eine lange Liste von Bedingungen.
  • Nesting Nested lambdas sind wirklich, wirklich schwer zu lesen.

Gehen Sie nicht über Bord. Denken Sie daran, dass die Software leicht geändert werden kann. Wenn Sie Zweifel haben, schreiben Sie es in beide Richtungen und sehen Sie, welche leichter zu lesen sind.


1
Es ist auch wichtig, die Datenmenge zu berücksichtigen, die durch das Lambda verarbeitet werden soll, da Lambdas bei geringen Datenverarbeitungsmengen eher ineffizient sind. Sie glänzen wirklich in der Parallelisierung großer Datenmengen.
CraigR8806

1
@ CraigR8806 Ich glaube nicht, dass die Leistung hier ein Problem darstellt. Das Verwenden einer anonymen Funktion oder das Erstellen einer Klasse, die die Funktionsschnittstelle erweitert, sollte in Bezug auf die Leistung identisch sein. Überlegungen zur Leistung ergeben sich, wenn Sie über die Verwendung der APIs für Streams / Funktionen höherer Ordnung über eine imperative Stilschleife sprechen. In dieser Frage verwenden wir jedoch in beiden Fällen funktionale Schnittstellen nur mit unterschiedlicher Syntax.
Puhlen

17
"Obwohl es in Ordnung ist, sehr einfache, auseinander liegende Lambdas zu wiederholen" Nur wenn sie sich nicht unbedingt gemeinsam ändern. Wenn sie müssen alle die gleiche Logik für Ihren Code korrekt sein sein, sollten Sie sie machen alle eigentlich das gleiche Verfahren , so dass Änderungen nur an einer Stelle vorgenommen werden müssen.
jpmc26

Insgesamt ist dies eine wirklich gute Antwort. Das Erwähnen der Verwendung einer Methodenreferenz als Alternative zu einem Lambda würde nur das Loch betreffen, das ich in dieser Antwort sehe.
Morgen

3
Wenn Sie Zweifel haben, schreiben Sie es in beide Richtungen und fragen Sie jemanden, der den Code verwaltet, der leichter zu lesen ist.
CorsiKa

14

Es hängt davon ab, ob. Immer wenn Sie feststellen, dass Sie dasselbe Lambda an verschiedenen Stellen verwenden, sollten Sie eine Klasse implementieren, die die Schnittstelle implementiert. Aber wenn du sonst eine anonyme innere Klasse benutzt hättest, denke ich, dass ein Lambda weitaus besser ist.


6
Vergessen Sie nicht, eine Methodenreferenz zu verwenden! Oracle hat aus genau diesem Grund statische Methoden und Standardmethoden überall hinzugefügt (und fügt in Java 9 noch mehr hinzu).
Jörg W Mittag

13

Ich unterstütze die Antwort von Karl Bielefeldt, möchte aber kurz ergänzen.

  • Debuggen Einige IDEs haben Probleme mit dem Gültigkeitsbereich in einem Lambda und haben Probleme, Membervariablen im Kontext eines Lambda anzuzeigen. Obwohl sich diese Situation hoffentlich auf der ganzen Linie ändern wird, kann es ärgerlich sein, den Code eines anderen zu pflegen, wenn er mit Lambdas übersät ist.

Auf jeden Fall für .NET. Lässt mich nicht unbedingt Lambdas meiden, aber ich fühle mich auf keinen Fall schuldig, wenn ich stattdessen etwas anderes tue.
user1172763

3
In diesem Fall verwenden Sie die falsche IDE. Sowohl IntelliJ als auch Netbeans schneiden in diesem Bereich recht gut ab.
David Foerster

1
@ user1172763 Wenn Sie in Visual Studio Mitgliedervariablen anzeigen möchten, können Sie den Aufrufstapel nach oben verschieben, bis Sie zum Lambda-Kontext gelangen.
Zev Spitz

6

Zugriff auf lokale Variablen des einschließenden Gültigkeitsbereichs

Die akzeptierte Antwort von Karl Bielefeldt ist richtig. Ich kann noch einen Unterschied machen:

  • Umfang

Der Lambda-Code, der in einer Methode innerhalb einer Klasse verschachtelt ist, kann auf alle effektiv endgültigen Variablen zugreifen , die in dieser Methode und Klasse gefunden wurden.

Durch das Erstellen einer Klasse, die die Funktionsschnittstelle implementiert, erhalten Sie keinen direkten Zugriff auf den Status des aufrufenden Codes.

So zitieren Sie das Java-Tutorial (Hervorhebung von mir):

Wie lokale und anonyme Klassen können Lambda-Ausdrücke Variablen erfassen. Sie haben den gleichen Zugriff auf lokale Variablen des einschließenden Bereichs . Im Gegensatz zu lokalen und anonymen Klassen weisen Lambda-Ausdrücke jedoch keine Shadowing-Probleme auf (weitere Informationen finden Sie unter Shadowing). Lambda-Ausdrücke haben einen lexikalischen Gültigkeitsbereich. Dies bedeutet, dass sie keine Namen von einem Supertyp erben oder eine neue Ebene des Geltungsbereichs einführen. Deklarationen in einem Lambda-Ausdruck werden genauso interpretiert wie in der umgebenden Umgebung.

Das Herausziehen und Benennen von langem Code hat also Vorteile, aber Sie müssen dies gegen die Einfachheit des direkten Zugriffs auf den Status der einschließenden Methode und Klasse abwägen.

Sehen:


4

Das mag ein Trottel sein, aber zu all den anderen hervorragenden Punkten, die in anderen Antworten gemacht wurden, möchte ich hinzufügen:

Bevorzugen Sie nach Möglichkeit Methodenreferenzen . Vergleichen Sie:

employees.stream()
         .map(Employee::getName)
         .forEach(System.out::println);

gegen

employees.stream()
         .map(employee -> employee.getName())
         .forEach(employeeName -> System.out.println(employeeName));

Wenn Sie eine Methodenreferenz verwenden, müssen Sie die Argumente des Lambda nicht mehr benennen, da diese normalerweise redundant sind und / oder zu faulen Namen wie eoder führen x.


0

Aufbauend auf der Antwort von Matt McHenry kann es eine Beziehung zwischen Lambda und Methodenreferenzen geben, wobei das Lambda als eine Reihe von Methodenreferenzen umgeschrieben werden kann. Zum Beispiel:

employees.stream()
         .forEach(employeeName -> System.out.println(employee.getName()));

vs

employees.stream()
         .map(Employee::getName)
         .forEach(System.out::println);
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.