Java8 Lambdas vs Anonyme Klassen

111

Da Java8 kürzlich veröffentlicht wurde und seine brandneuen Lambda-Ausdrücke wirklich cool aussehen, habe ich mich gefragt, ob dies den Niedergang der anonymen Klassen bedeutet, an die wir so gewöhnt waren.

Ich habe ein wenig darüber recherchiert und einige coole Beispiele dafür gefunden, wie Lambda-Ausdrücke diese Klassen systematisch ersetzen, wie z. B. die Sortiermethode der Sammlung, mit der eine anonyme Instanz von Comparator die Sortierung durchführen konnte:

Collections.sort(personList, new Comparator<Person>(){
  public int compare(Person p1, Person p2){
    return p1.firstName.compareTo(p2.firstName);
  }
});

Jetzt kann mit Lambdas gemacht werden:

Collections.sort(personList, (Person p1, Person p2) -> p1.firstName.compareTo(p2.firstName));

Und sieht überraschend prägnant aus. Meine Frage ist also, gibt es einen Grund, diese Klassen weiterhin in Java8 anstelle von Lambdas zu verwenden?

BEARBEITEN

Dieselbe Frage, aber in umgekehrter Richtung: Welche Vorteile bietet die Verwendung von Lambdas anstelle von anonymen Klassen, da Lambdas nur mit Schnittstellen für einzelne Methoden verwendet werden kann. Ist diese neue Funktion nur eine Verknüpfung, die nur in wenigen Fällen verwendet wird, oder ist sie wirklich nützlich?

Amin Abu-Taleb
quelle
5
Sicher, für all jene anonymen Klassen, die Methoden mit Nebenwirkungen bereitstellen.
tobias_k
11
Nur zu Ihrer Information können Sie den Komparator auch so konstruieren Comparator.comparing(Person::getFirstName), getFirstName()dass: eine Methode zurückgegeben wird firstName.
Skiwi
1
Oder anonyme Klassen mit mehreren Methoden oder ...
Mark Rotteveel
1
Ich bin versucht, für knapp als zu breit zu stimmen, insbesondere aufgrund der zusätzlichen Fragen nach EDIT .
Mark Rotteveel
1
Ein schöner ausführlicher Artikel zu diesem Thema: infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood
Ram Patra

Antworten:

107

Eine anonyme innere Klasse (AIC) kann verwendet werden, um eine Unterklasse einer abstrakten Klasse oder einer konkreten Klasse zu erstellen. Ein AIC kann auch eine konkrete Implementierung einer Schnittstelle bereitstellen, einschließlich des Hinzufügens von Status (Feldern). Eine Instanz eines AIC kann thisin seinen Methodenkörpern verwendet werden, sodass weitere Methoden aufgerufen werden können, sein Zustand im Laufe der Zeit mutiert werden kann usw. Keine dieser Aussagen gilt für Lambdas.

Ich würde vermuten, dass die Mehrheit der Verwendungen von AICs darin bestand, zustandslose Implementierungen einzelner Funktionen bereitzustellen und daher durch Lambda-Ausdrücke ersetzt werden können, aber es gibt andere Verwendungen von AICs, für die Lambdas nicht verwendet werden können. AICs sind hier, um zu bleiben.

AKTUALISIEREN

Ein weiterer Unterschied zwischen AICs und Lambda-Ausdrücken besteht darin, dass AICs einen neuen Bereich einführen. Das heißt, Namen werden aus den Superklassen und Schnittstellen des AIC aufgelöst und können Namen beschatten, die in der lexikalisch einschließenden Umgebung auftreten. Bei Lambdas werden alle Namen lexikalisch aufgelöst.

Stuart Marks
quelle
1
Lambdas können Staat haben. In dieser Hinsicht sehe ich keinen Unterschied zwischen Lambdas und AIC.
Nosid
1
@nosid AICs können wie Instanzen einer Klasse den Status in Feldern enthalten, und dieser Status ist für jede Methode der Klasse zugänglich (und möglicherweise durch diese veränderbar). Dieser Status bleibt bestehen, bis das Objekt GC-fähig ist, dh eine unbestimmte Ausdehnung aufweist, sodass es über Methodenaufrufe hinweg bestehen bleibt. Der einzige Staat mit unbestimmter Ausdehnung, den Lambdas haben, wird zum Zeitpunkt der Begegnung mit dem Lambda gefangen genommen. Dieser Zustand ist unveränderlich. Lokale Variablen innerhalb eines Lambdas sind veränderbar, existieren jedoch nur, während ein Lambda-Aufruf ausgeführt wird.
Stuart Marks
1
@nosid Ah, der Single-Element-Array-Hack. Versuchen Sie einfach nicht, Ihren Zähler aus mehreren Threads zu verwenden. Wenn Sie dem Heap etwas zuweisen und es in einem Lambda erfassen möchten, können Sie auch einen AIC verwenden und ein Feld hinzufügen, das Sie direkt mutieren können. Die Verwendung eines Lambda auf diese Weise kann funktionieren, aber warum sollte man sich die Mühe machen, wenn man ein reales Objekt verwenden kann?
Stuart Marks
2
AIC erstellt eine Datei wie diese, A$1.classLambda jedoch nicht. Kann ich dies in Differenz hinzufügen?
Asif Mushtaq
2
@UnKnown Das ist hauptsächlich ein Implementierungsproblem. Es hat keinen Einfluss darauf, wie man mit AICs gegen Lambdas programmiert, worum es bei dieser Frage hauptsächlich geht. Beachten Sie, dass ein Lambda-Ausdruck eine Klasse mit einem Namen wie generiert LambdaClass$$Lambda$1/1078694789. Diese Klasse wird jedoch im laufenden Betrieb von der Lambda-Metafabrik generiert, nicht von javac, sodass keine entsprechende .classDatei vorhanden ist. Dies ist jedoch wiederum ein Implementierungsproblem.
Stuart Marks
60

Lambdas ist zwar eine großartige Funktion, funktioniert aber nur mit SAM-Typen. Das heißt, Schnittstellen mit nur einer einzigen abstrakten Methode. Es würde fehlschlagen, sobald Ihre Schnittstelle mehr als eine abstrakte Methode enthält. Hier sind anonyme Klassen hilfreich.

Nein, wir können anonyme Klassen nicht einfach ignorieren. Und nur zu Ihrer Information, Ihre sort()Methode kann vereinfacht werden, indem Sie die Typdeklaration für p1und überspringen p2:

Collections.sort(personList, (p1, p2) -> p1.firstName.compareTo(p2.firstName));

Sie können hier auch die Methodenreferenz verwenden. Entweder fügen Sie der Klasse eine compareByFirstName()Methode hinzu Personund verwenden:

Collections.sort(personList, Person::compareByFirstName);

oder fügen Sie einen Getter hinzu firstName, um direkt die Comparatorfrom- Comparator.comparing()Methode zu erhalten:

Collections.sort(personList, Comparator.comparing(Person::getFirstName));
Rohit Jain
quelle
4
Ich wusste es, aber ich bevorzuge die lange in Bezug auf die Lesbarkeit, da es sonst verwirrend sein könnte, herauszufinden, woher diese Variablen kommen.
Amin Abu-Taleb
@ AminAbu-Taleb Warum sollte es verwirrend sein. Das ist eine gültige Lambda-Syntax. Die Typen werden sowieso abgeleitet. Wie auch immer, es ist eine persönliche Entscheidung. Sie können Typen explizit angeben. Keine Probleme.
Rohit Jain
1
Es gibt einen weiteren subtilen Unterschied zwischen Lambdas und anonymen Klassen: Anonyme Klassen können direkt mit den neuen Java 8 Type Annotations kommentiert werden , z new @MyTypeAnnotation SomeInterface(){};. Dies ist für Lambda-Ausdrücke nicht möglich. Einzelheiten finden Sie in meiner Frage hier: Kommentieren der Funktionsschnittstelle eines Lambda-Ausdrucks .
Balder
36

Lambda-Leistung mit anonymen Klassen

Beim Start der Anwendung muss jede Klassendatei geladen und überprüft werden.

Anonyme Klassen werden vom Compiler als neuer Subtyp für die angegebene Klasse oder Schnittstelle verarbeitet, sodass für jede Klasse eine neue Klassendatei generiert wird.

Lambdas unterscheiden sich bei der Bytecode-Generierung. Sie sind effizienter und verwenden die mit JDK7 gelieferte invokedynamic-Anweisung.

Für Lambdas wird diese Anweisung verwendet, um die Übersetzung des Lambda-Ausdrucks im Bytecode bis zur Laufzeit zu verzögern. (Anweisung wird nur zum ersten Mal aufgerufen)

Als Ergebnis wird der Lambda-Ausdruck zu einer statischen Methode (zur Laufzeit erstellt). (Es gibt einen kleinen Unterschied zu stateles und statefull Fällen, sie werden über generierte Methodenargumente aufgelöst)

Dmitriy Kuzkin
quelle
Jedes Lambda benötigt auch eine neue Klasse, die jedoch zur Laufzeit generiert wird. In diesem Sinne sind Lambdas nicht effizienter als anonyme Klassen. Lambdas werden erstellt, über invokedynamicdie im Allgemeinen langsamer als invokespecialzum Erstellen neuer Instanzen anonymer Klassen verwendet wird. In diesem Sinne sind Lambdas auch langsamer (JVM kann invokedynamicAnrufe jedoch die meiste Zeit optimieren ).
ZhekaKozlov
3
@AndreiTomashpolskiy 1. Bitte sei höflich. 2. Lesen Sie diesen Kommentar eines Compiler-Ingenieurs: habrahabr.ru/post/313350/comments/#comment_9885460
ZhekaKozlov
@ZhekaKozlov, Sie müssen kein Compiler-Ingenieur sein, um JRE-Quellcode zu lesen und Javap / Debugger zu verwenden. Was Sie vermissen, ist, dass die Generierung einer Wrapper-Klasse für eine Lambda-Methode vollständig im Arbeitsspeicher erfolgt und so gut wie nichts kostet, während das Instanziieren eines AIC das Auflösen und Laden der entsprechenden Klassenressource umfasst (was einen E / A-Systemaufruf bedeutet). Daher ist die invokedynamicGenerierung von Ad-hoc-Klassen im Vergleich zu kompilierten anonymen Klassen sehr schnell.
Andrei Tomashpolskiy
@ AndreiTomashpolskiy I / O ist nicht unbedingt langsam
ZhekaKozlov
14

Es gibt folgende Unterschiede:

1) Syntax

Lambda-Ausdrücke sehen im Vergleich zu Anonymous Inner Class (AIC) ordentlich aus

public static void main(String[] args) {
    Runnable r = new Runnable() {
        @Override
        public void run() {
            System.out.println("in run");
        }
    };

    Thread t = new Thread(r);
    t.start(); 
}

//syntax of lambda expression 
public static void main(String[] args) {
    Runnable r = ()->{System.out.println("in run");};
    Thread t = new Thread(r);
    t.start();
}

2) Geltungsbereich

Eine anonyme innere Klasse ist eine Klasse, dh sie hat einen Bereich für Variablen, die innerhalb der inneren Klasse definiert sind.

Der Lambda-Ausdruck ist kein eigener Bereich, sondern Teil des einschließenden Bereichs.

Eine ähnliche Regel gilt für super und dieses Schlüsselwort, wenn innerhalb einer anonymen inneren Klasse und eines Lambda-Ausdrucks verwendet wird. Bei einer anonymen inneren Klasse bezieht sich dieses Schlüsselwort auf den lokalen Bereich und das Super-Schlüsselwort auf die Superklasse der anonymen Klasse. Während im Fall eines Lambda-Ausdrucks dieses Schlüsselwort auf das Objekt des umschließenden Typs verweist und super auf die Superklasse der umschließenden Klasse verweist.

//AIC
    public static void main(String[] args) {
        final int cnt = 0; 
        Runnable r = new Runnable() {
            @Override
            public void run() {
                int cnt = 5;    
                System.out.println("in run" + cnt);
            }
        };

        Thread t = new Thread(r);
        t.start();
    }

//Lambda
    public static void main(String[] args) {
        final int cnt = 0; 
        Runnable r = ()->{
            int cnt = 5; //compilation error
            System.out.println("in run"+cnt);};
        Thread t = new Thread(r);
        t.start();
    }

3) Leistung

Zur Laufzeit erfordern anonyme innere Klassen das Laden von Klassen, die Speicherzuweisung sowie die Objektinitialisierung und den Aufruf einer nicht statischen Methode, während der Lambda-Ausdruck eine reine Aktivität zur Kompilierungszeit ist und zur Laufzeit keine zusätzlichen Kosten verursacht. Die Leistung des Lambda-Ausdrucks ist also besser als bei anonymen inneren Klassen. **

** Mir ist klar, dass dieser Punkt nicht ganz richtig ist. Weitere Informationen finden Sie in der folgenden Frage. Lambda gegen anonyme innere Klassenleistung: Reduzierung der Belastung des ClassLoader?

atom217
quelle
3

Lambdas in Java 8 wurde für die funktionale Programmierung eingeführt. Wo Sie Boilerplate-Code vermeiden können. Ich bin auf diesen interessanten Artikel über Lambda gestoßen.

http://radar.oreilly.com/2014/04/whats-new-in-java-8-lambdas.html

Es ist ratsam, Lambda-Funktionen für einfache Logik zu verwenden. Wenn die Implementierung komplexer Logik mit Lambdas im Fehlerfall ein Overhead beim Debuggen des Codes ist.

Tim
quelle
0

Anonyme Klassen sind da, um zu bleiben, weil Lambda gut für Funktionen mit einzelnen abstrakten Methoden ist, aber in allen anderen Fällen sind anonyme innere Klassen Ihr Retter.

geräumig
quelle
-1
  • Für die Lambda-Syntax muss nicht der offensichtliche Code geschrieben werden, auf den Java schließen kann.
  • Durch die Verwendung invoke dynamicwird Lambda während der Kompilierungszeit nicht zurück in die anonymen Klassen konvertiert (Java muss keine Objekte erstellen, sondern muss sich nur um die Signatur der Methode kümmern, kann sich an eine Methode binden, ohne ein Objekt zu erstellen
  • Lambda legte mehr Wert darauf, was wir tun wollen, als auf das, was wir tun müssen, bevor wir es tun können
MagGGG
quelle