Haben Lambda-Ausdrücke eine andere Verwendung als das Speichern von Codezeilen?

120

Haben Lambda-Ausdrücke eine andere Verwendung als das Speichern von Codezeilen?

Gibt es spezielle Funktionen von Lambdas, die Probleme lösten, die nicht einfach zu lösen waren? Die typische Verwendung, die ich gesehen habe, ist die, anstatt dies zu schreiben:

Comparator<Developer> byName = new Comparator<Developer>() {
  @Override
  public int compare(Developer o1, Developer o2) {
    return o1.getName().compareTo(o2.getName());
  }
};

Wir können einen Lambda-Ausdruck verwenden, um den Code zu verkürzen:

Comparator<Developer> byName =
(Developer o1, Developer o2) -> o1.getName().compareTo(o2.getName());
Vikash
quelle
27
Beachten Sie, dass es noch kürzer sein kann: (o1, o2) -> o1.getName().compareTo(o2.getName())
Thorbjørn Ravn Andersen
88
oder noch kürzer:Comparator.comparing(Developer::getName)
Thomas Kläger
31
Ich würde diesen Vorteil nicht unterschätzen. Wenn die Dinge so viel einfacher sind, ist es wahrscheinlicher, dass Sie die Dinge "richtig" machen. Es gibt oft Spannungen zwischen dem Schreiben von Code, der auf lange Sicht besser zu warten ist, und dem Aufschreiben auf kurze Sicht. Lambdas reduzieren diese Spannung und helfen Ihnen so, saubereren Code zu schreiben.
Yshavit
5
Lambdas sind Verschlüsse, wodurch die Platzersparnis noch größer wird, da Sie Ihrer Ersatzklasse jetzt keinen parametrisierten Konstruktor, keine Felder, keinen Zuweisungscode usw. hinzufügen müssen.
CodesInChaos

Antworten:

116

Lambda-Ausdrücke ändern nicht die Probleme, die Sie mit Java im Allgemeinen lösen können, erleichtern jedoch definitiv die Lösung bestimmter Probleme, nur aus dem gleichen Grund, aus dem wir nicht mehr in Assemblersprache programmieren. Das Entfernen redundanter Aufgaben aus der Arbeit des Programmierers erleichtert das Leben und ermöglicht es, Dinge zu tun, die Sie sonst nicht einmal berühren würden, nur für die Menge an Code, die Sie (manuell) produzieren müssten.

Lambda-Ausdrücke speichern jedoch nicht nur Codezeilen. Mit Lambda-Ausdrücken können Sie Funktionen definieren , für die Sie zuvor anonyme innere Klassen als Problemumgehung verwenden konnten. Deshalb können Sie in diesen Fällen anonyme innere Klassen ersetzen, jedoch nicht generell.

Insbesondere werden Lambda-Ausdrücke unabhängig von der Funktionsschnittstelle definiert, in die sie konvertiert werden, sodass keine geerbten Mitglieder vorhanden sind, auf die sie zugreifen können. Außerdem können sie nicht auf die Instanz des Typs zugreifen, der die Funktionsschnittstelle implementiert. Siehe auch diese Antwort innerhalb eines Lambda-Ausdrucks thisund supermit derselben Bedeutung wie im umgebenden Kontext . Sie können auch keine neuen lokalen Variablen erstellen, die lokale Variablen des umgebenden Kontexts überschatten. Für die beabsichtigte Aufgabe, eine Funktion zu definieren, werden viele Fehlerquellen entfernt, aber es wird auch impliziert, dass es für andere Anwendungsfälle anonyme innere Klassen geben kann, die nicht in einen Lambda-Ausdruck konvertiert werden können, selbst wenn eine funktionale Schnittstelle implementiert wird.

Darüber hinaus new Type() { … }garantiert das Konstrukt (wie newimmer) , eine neue eindeutige Instanz zu erzeugen . Anonyme Instanzen der inneren Klasse behalten immer einen Verweis auf ihre äußere Instanz, wenn sie in einem Nicht- staticKontext erstellt werden. Im Gegensatz dazu erfassen Lambda-Ausdrücke nur dann einen Verweis, thiswenn dies erforderlich ist, dh wenn sie darauf zugreifen thisoder nicht staticMitglied sind. Und sie erzeugen Instanzen einer absichtlich nicht spezifizierten Identität, wodurch die Implementierung zur Laufzeit entscheiden kann, ob vorhandene Instanzen wiederverwendet werden sollen (siehe auch „ Erstellt ein Lambda-Ausdruck bei jeder Ausführung ein Objekt auf dem Heap? “).

Diese Unterschiede gelten für Ihr Beispiel. Ihr anonymes inneres Klassenkonstrukt erzeugt immer eine neue Instanz. Außerdem erfasst es möglicherweise einen Verweis auf die äußere Instanz, während es sich bei Ihrem (Developer o1, Developer o2) -> o1.getName().compareTo(o2.getName())nicht erfassenden Lambda-Ausdruck um einen Singleton handelt, der in typischen Implementierungen als Singleton ausgewertet wird. Außerdem wird keine .classDatei auf Ihrer Festplatte erstellt.

Angesichts der Unterschiede sowohl in Bezug auf die Semantik als auch in Bezug auf die Leistung können Lambda-Ausdrücke die Art und Weise verändern, wie Programmierer bestimmte Probleme in Zukunft lösen werden, natürlich auch aufgrund der neuen APIs, die Ideen der funktionalen Programmierung unter Verwendung der neuen Sprachfunktionen enthalten. Siehe auch Java 8 Lambda-Ausdruck und erstklassige Werte .

Holger
quelle
1
Im Wesentlichen sind Lambda-Ausdrücke eine Art funktionale Programmierung. Angesichts der Tatsache, dass jede Aufgabe mit funktionaler Programmierung oder zwingender Programmierung erledigt werden kann , gibt es keinen Vorteil außer der Lesbarkeit und Wartbarkeit , die andere Bedenken überwiegen können.
Draco18s vertraut SE
1
@Trilarion: Sie könnten ganze Bücher mit der Definition der Funktion füllen . Sie müssten sich auch entscheiden, ob Sie sich auf das Ziel oder auf die Aspekte konzentrieren möchten, die von Javas Lambda-Ausdrücken unterstützt werden. Der letzte Link meiner Antwort ( dieser ) behandelt bereits einige dieser Aspekte im Kontext von Java. Aber um meine Antwort zu verstehen, reicht es bereits zu verstehen, dass Funktionen ein anderes Konzept als Klassen sind. Für weitere Details gibt es bereits vorhandene Ressourcen und Fragen und Antworten…
Holger
1
@Trilarion: Beide Funktionen ermöglichen es nicht, weitere Probleme zu lösen, es sei denn, Sie betrachten die Menge an Codegröße / -komplexität oder Problemumgehungen, die von anderen Lösungen benötigt werden, als inakzeptabel (wie im ersten Absatz erwähnt), und es gab bereits eine Möglichkeit, Funktionen zu definieren Wie gesagt, innere Klassen könnten als Problemumgehung verwendet werden, wenn es keine bessere Möglichkeit gibt, Funktionen zu definieren (innere Klassen sind jedoch keine Funktionen, da sie viel mehr Zwecken dienen können). Es ist dasselbe wie bei OOP - es erlaubt nichts, was Sie mit Assembly nicht konnten, aber können Sie sich dennoch eine Anwendung wie Eclipse vorstellen, die in Assembly geschrieben wurde?
Holger
3
@EricDuminil: Für mich ist die Turing-Vollständigkeit zu theoretisch. Unabhängig davon, ob Java vollständig ist oder nicht, können Sie keinen Windows-Gerätetreiber in Java schreiben. (Oder in PostScript, das ebenfalls vollständig ist.) Das Hinzufügen von Funktionen zu Java, die dies ermöglichen, würde die Menge der Probleme ändern, die Sie damit lösen können. Dies wird jedoch nicht passieren. Also bevorzuge ich den Sammelpunkt; Da alles, was Sie tun können, in ausführbaren CPU-Anweisungen implementiert ist, können Sie in Assembly nichts tun, was jede CPU-Anweisung unterstützt (auch einfacher zu verstehen als der Turing-Maschinenformalismus).
Holger
2
@abelito ist allen übergeordneten Programmierkonstrukten gemeinsam, um weniger „Einblick in das, was tatsächlich vor sich geht“ zu bieten, da es darum geht, weniger Details, mehr Fokus auf das eigentliche Programmierproblem. Sie können es verwenden, um Code besser oder schlechter zu machen. Es ist nur ein Werkzeug. Wie die Abstimmungsfunktion; Es ist ein Werkzeug, das Sie auf die beabsichtigte Weise verwenden können, um ein fundiertes Urteil über die tatsächliche Qualität eines Beitrags zu fällen. Oder Sie verwenden es, um eine ausgearbeitete, vernünftige Antwort abzulehnen, nur weil Sie Lambdas hassen.
Holger
52

Programmiersprachen können nicht von Maschinen ausgeführt werden.

Sie sind für Programmierer gedacht .

Sprachen sind ein Gespräch mit einem Compiler, um unsere Gedanken in etwas zu verwandeln, das eine Maschine ausführen kann. Eine der wichtigsten Beschwerden über Java von Menschen , die aus anderen Sprachen zu ihm kommen (oder lassen es für andere Sprachen) verwendet sein , dass es zwingt ein gewisses mentales Modell auf dem Programmiergerät (dh alles ist eine Klasse).

Ich werde nicht abwägen, ob das gut oder schlecht ist: Alles ist ein Kompromiss. Mit Java 8-Lambdas können Programmierer jedoch in Funktionen denken , was in Java bisher nicht möglich war.

Es ist dasselbe wie ein prozeduraler Programmierer, der lernt, in Klassen zu denken , wenn sie zu Java kommen: Sie sehen, wie sie sich allmählich von Klassen entfernen, die verherrlichte Strukturen sind und 'Helfer'-Klassen mit einer Reihe statischer Methoden haben, und zu etwas übergehen, das ähnelt eher einem rationalen OO-Design (mea culpa).

Wenn Sie sie nur als eine kürzere Möglichkeit betrachten, anonyme innere Klassen auszudrücken, werden Sie sie wahrscheinlich nicht so beeindruckend finden, wie der oben beschriebene prozedurale Programmierer wahrscheinlich nicht dachte, dass Klassen eine große Verbesserung darstellen.

Jared Smith
quelle
5
Seien Sie vorsichtig damit, ich dachte immer, dass OOP alles ist, und dann wurde ich schrecklich verwirrt mit Entity-Component-System-Architekturen (was bedeutet, dass Sie nicht für jede Art von Spielobjekt eine Klasse haben?! Und jedes Spielobjekt ist kein OOP-Objekt
?!
3
Mit anderen Worten, das Denken in OOP, und nicht nur Denken * o f * Klassen als ein weiteres Werkzeug, y beschränkte mein Denken in einem schlechten Weg.
user253751
2
@immibis: Es gibt viele verschiedene Möglichkeiten, in OOP zu denken. Abgesehen von dem häufigen Fehler, „Denken in OOP“ mit „Denken in Klassen“ zu verwechseln, gibt es viele Möglichkeiten, kontraproduktiv zu denken. Leider passiert das von Anfang an zu oft, da selbst Bücher und Tutorials die minderwertigen lehren, in Beispielen Anti-Muster zeigen und dieses Denken verbreiten. Die angenommene Notwendigkeit, „für jede Art von Klasse eine Klasse zu haben“, ist nur ein Beispiel, das dem Konzept der Abstraktion widerspricht. Ebenso scheint es den Mythos „OOP bedeutet, so viel Boilerplate wie möglich zu schreiben“ usw. zu geben
Holger,
@immibis Obwohl es Ihnen in diesem Fall nicht geholfen hat, unterstützt diese Anekdote tatsächlich den Punkt: Die Sprache und das Paradigma bieten einen Rahmen, um Ihre Gedanken zu strukturieren und sie in ein Design umzuwandeln. In Ihrem Fall sind Sie gegen die Ränder des Frameworks gelaufen, aber in einem anderen Kontext hätte Ihnen dies möglicherweise geholfen, sich nicht in einen großen Schlammball zu verirren und ein hässliches Design zu erstellen. Daher gehe ich davon aus, dass "alles Kompromisse sind". Der letztgenannte Vorteil bei gleichzeitiger Vermeidung des erstgenannten Problems ist eine Schlüsselfrage bei der Entscheidung, wie "groß" eine Sprache sein soll.
Leushenko
@immibis Deshalb ist es wichtig, eine Reihe von Paradigmen gut zu lernen : Jeder erreicht Sie über die Unzulänglichkeiten der anderen.
Jared Smith
36

Das Speichern von Codezeilen kann als neue Funktion angesehen werden, wenn Sie damit einen wesentlichen Teil der Logik kürzer und klarer schreiben können, was für andere weniger Zeit zum Lesen und Verstehen benötigt.

Ohne Lambda-Ausdrücke (und / oder Methodenreferenzen) Streamwären Pipelines viel weniger lesbar gewesen.

Stellen Sie sich zum Beispiel vor, wie die folgende StreamPipeline ausgesehen hätte, wenn Sie jeden Lambda-Ausdruck durch eine anonyme Klasseninstanz ersetzt hätten.

List<String> names =
    people.stream()
          .filter(p -> p.getAge() > 21)
          .map(p -> p.getName())
          .sorted((n1,n2) -> n1.compareToIgnoreCase(n2))
          .collect(Collectors.toList());

Es wäre:

List<String> names =
    people.stream()
          .filter(new Predicate<Person>() {
              @Override
              public boolean test(Person p) {
                  return p.getAge() > 21;
              }
          })
          .map(new Function<Person,String>() {
              @Override
              public String apply(Person p) {
                  return p.getName();
              }
          })
          .sorted(new Comparator<String>() {
              @Override
              public int compare(String n1, String n2) {
                  return n1.compareToIgnoreCase(n2);
              }
          })
          .collect(Collectors.toList());

Dies ist viel schwieriger zu schreiben als die Version mit Lambda-Ausdrücken und viel fehleranfälliger. Es ist auch schwerer zu verstehen.

Und das ist eine relativ kurze Pipeline.

Um dies ohne Lambda-Ausdrücke und Methodenreferenzen lesbar zu machen, hätten Sie Variablen definieren müssen, die die verschiedenen hier verwendeten funktionalen Schnittstelleninstanzen enthalten, wodurch die Logik der Pipeline aufgeteilt und das Verständnis erschwert worden wäre.

Eran
quelle
17
Ich denke, das ist eine wichtige Erkenntnis. Es könnte argumentiert werden, dass etwas in einer Programmiersprache praktisch unmöglich ist, wenn der syntaktische und kognitive Aufwand zu groß ist, um nützlich zu sein, so dass andere Ansätze überlegen sind. Durch die Reduzierung des syntaktischen und kognitiven Overheads (wie es Lambdas im Vergleich zu anonymen Klassen tun) werden Pipelines wie die oben genannten möglich. Noch ironischer: Wenn Sie durch das Schreiben eines Codes von Ihrem Job entlassen werden, kann man sagen, dass dies nicht möglich ist.
Sebastian Redl
Nitpick: Sie brauchen nicht eigentlich die Comparatorfür sortedda es ohnehin in natürlicher Ordnung vergleicht. Vielleicht ändern Sie es, um die Länge von Zeichenfolgen oder ähnliches zu vergleichen, um das Beispiel noch besser zu machen?
tobias_k
@tobias_k kein Problem. Habe ich es zu , compareToIgnoreCaseum es anders zu machen verhalten als sorted().
Eran
1
compareToIgnoreCaseist immer noch ein schlechtes Beispiel, da Sie einfach den vorhandenen String.CASE_INSENSITIVE_ORDERKomparator verwenden könnten . Der Vorschlag von @ tobias_k, nach Länge zu vergleichen (oder jede andere Eigenschaft, die keinen eingebauten Komparator hat), passt besser. Nach SO Q & A-Codebeispielen zu urteilen, verursachten Lambdas viele unnötige Komparatorimplementierungen…
Holger,
Bin ich der einzige, der glaubt, das zweite Beispiel sei leichter zu verstehen?
Clark Battle
8

Interne Iteration

Wenn Java Collections iterieren, neigen die meisten Entwickler zu bekommen , ein Element und dann verarbeiten sie. Nehmen Sie das Element heraus und verwenden Sie es dann oder fügen Sie es erneut ein usw. Mit Java-Versionen vor 8 können Sie eine innere Klasse implementieren und Folgendes tun:

numbers.forEach(new Consumer<Integer>() {
    public void accept(Integer value) {
        System.out.println(value);
    }
});

Mit Java 8 können Sie jetzt besser und weniger ausführlich vorgehen mit:

numbers.forEach((Integer value) -> System.out.println(value));

oder besser

numbers.forEach(System.out::println);

Verhalten als Argumente

Erraten Sie den folgenden Fall:

public int sumAllEven(List<Integer> numbers) {
    int total = 0;

    for (int number : numbers) {
        if (number % 2 == 0) {
            total += number;
        }
    } 
    return total;
}

Mit der Java 8 Predicate-Oberfläche können Sie Folgendes besser tun:

public int sumAll(List<Integer> numbers, Predicate<Integer> p) {
    int total = 0;

    for (int number : numbers) {
        if (p.test(number)) {
            total += number;
        }
    }
    return total;
}

Nennen wir es wie:

sumAll(numbers, n -> n % 2 == 0);

Quelle: DZone - Warum wir Lambda-Ausdrücke in Java benötigen

alseether
quelle
Wahrscheinlich ist der erste Fall eine Möglichkeit, einige Codezeilen zu speichern, aber es erleichtert das Lesen des Codes
auch
4
Wie ist die interne Iteration eine Lambda-Funktion? Die einfache Tatsache ist, dass Lambdas Sie technisch gesehen nichts tun lassen, was Sie vor Java-8 nicht konnten. Es ist nur eine präzisere Möglichkeit, Code weiterzugeben.
Ousmane D.
@ Aominè In diesem Fall besteht numbers.forEach((Integer value) -> System.out.println(value));der Lambda-Ausdruck aus zwei Teilen, dem linken links neben dem Pfeilsymbol (->), in dem die Parameter aufgeführt sind, und dem rechten Teil , der den Körper enthält . Der Compiler stellt automatisch fest, dass der Lambda-Ausdruck dieselbe Signatur wie die einzige nicht implementierte Methode der Consumer-Schnittstelle hat.
21.
5
Ich habe nicht gefragt, was ein Lambda-Ausdruck ist, ich sage nur, dass interne Iteration nichts mit Lambda-Ausdrücken zu tun hat.
Ousmane D.
1
@ Aominè Ich verstehe Ihren Standpunkt, es hat nichts mit Lambdas an sich , aber wie Sie betonen, ist es eher eine sauberere Art zu schreiben. Ich denke, in Bezug auf Effizienz ist so gut wie vor 8 Versionen
21.
4

Es gibt viele Vorteile der Verwendung von Lambdas anstelle der folgenden inneren Klasse:

  • Machen Sie den Code kompakter und ausdrucksvoller, ohne mehr Sprachsyntax-Semantik einzuführen. Sie haben in Ihrer Frage bereits ein Beispiel gegeben.

  • Durch die Verwendung von Lambdas können Sie gerne mit funktionalen Operationen an Elementströmen programmieren, z. B. Transformationen mit Kartenreduzierung für Sammlungen. Siehe Dokumentation zu den Paketen java.util.function & java.util.stream .

  • Es gibt keine physische Klassendatei, die vom Compiler für Lambdas generiert wurde. Dadurch werden Ihre gelieferten Anwendungen kleiner. Wie weist Memory Lambda zu?

  • Der Compiler optimiert die Lambda-Erstellung, wenn das Lambda nicht auf Variablen außerhalb seines Gültigkeitsbereichs zugreift. Dies bedeutet, dass die Lambda-Instanz nur einmal von der JVM erstellt wird. Weitere Informationen finden Sie in der Antwort von @ Holger auf die Frage. Ist das Zwischenspeichern von Methodenreferenzen in Java 8 eine gute Idee? .

  • Lambdas kann neben der funktionalen Schnittstelle auch Multi-Marker-Schnittstellen implementieren, aber die anonymen inneren Klassen können keine weiteren Schnittstellen implementieren, zum Beispiel:

    //                 v--- create the lambda locally.
    Consumer<Integer> action = (Consumer<Integer> & Serializable) it -> {/*TODO*/};
Holi-Java
quelle
2

Lambdas sind nur syntaktischer Zucker für anonyme Klassen.

Vor Lambdas können anonyme Klassen verwendet werden, um dasselbe zu erreichen. Jeder Lambda-Ausdruck kann in eine anonyme Klasse konvertiert werden.

Wenn Sie IntelliJ IDEA verwenden, kann es die Konvertierung für Sie durchführen:

  • Setzen Sie den Cursor in das Lambda
  • Drücken Sie Alt / Option + Eingabetaste

Geben Sie hier die Bildbeschreibung ein

Kehrmaschine
quelle
3
... Ich dachte, @StuartMarks hat bereits die syntaktische Zuckerqualität (sp? Gr?) Von Lambdas geklärt? ( stackoverflow.com/a/15241404/3475600 , softwareengineering.stackexchange.com/a/181743 )
srborlongan
2

Um Ihre Frage zu beantworten, können Sie mit Lambdas nichts tun, was Sie vor Java-8 nicht konnten, sondern Sie können präziseren Code schreiben . Dies hat den Vorteil, dass Ihr Code klarer und flexibler wird.

Ousmane D.
quelle
Offensichtlich hat @Holger alle Zweifel an der Lambda-Effizienz zerstreut. Es ist nicht nur eine Möglichkeit, Code sauberer zu machen, sondern wenn Lambda keinen Wert erfasst, wird es eine Singleton-Klasse (wiederverwendbar) sein
auch
Wenn Sie tief in die Tiefe graben, hat jede Sprachfunktion einen Unterschied. Die vorliegende Frage ist auf hohem Niveau, daher habe ich meine Antwort auf hohem Niveau geschrieben.
Ousmane D.
2

Eine Sache, die ich noch nicht erwähnt habe, ist, dass Sie mit einem Lambda die Funktionalität dort definieren können, wo sie verwendet wird .

Wenn Sie also eine einfache Auswahlfunktion haben, müssen Sie diese nicht an einem separaten Ort mit einem Bündel Boilerplate platzieren. Sie schreiben einfach ein Lambda, das präzise und lokal relevant ist.

Carlos
quelle
1

Ja, es gibt viele Vorteile.

  • Da keine ganze Klasse definiert werden muss, können wir die Implementierung der Funktion selbst als Referenz übergeben.
    • Bei der internen Erstellung einer Klasse wird eine Klassendatei erstellt. Wenn Sie Lambda verwenden, wird die Klassenerstellung vom Compiler vermieden, da Sie in Lambda die Funktionsimplementierung anstelle der Klasse übergeben.
  • Die Wiederverwendbarkeit von Code ist höher als zuvor
  • Und wie Sie sagten, ist Code kürzer als die normale Implementierung.
Sunil Kanzar
quelle