Lambda diese Referenz in Java

73

Ich möchte ein anonymous classin ein konvertieren lambda expression. Aber diese anonyme Klasse benutze ich das thisSchlüsselwort.

Zum Beispiel habe ich dieses einfache Observer/ObservableMuster geschrieben:

import java.util.ArrayList;
import java.util.Collection;

public static class Observable {
    private final Collection<Observer> notifiables = new ArrayList<>();

    public Observable() { }

    public void addObserver(Observer notifiable) { notifiables.add(notifiable); }
    public void removeObserver(Observer notifiable) { notifiables.add(notifiable); }

    public void change() {
        notifiables.forEach(notifiable -> notifiable.changed(this));
    }
}

public interface Observer {
    void changed(Observable notifier);
}

und dieser Beispielcode mit einer anonymen Klasse (verwenden Sie das Schlüsselwort this):

public class Main {

    public static void main(String[] args) {
        Observable observable = new Observable();
        observable.addObserver(new Observer() {
            @Override
            public void changed(Observable notifier) {
                notifier.removeObserver(this);
            }
        });
        observable.change();
    }
}

aber wenn ich es in einen Lambda-Ausdruck konvertiere:

public class Main {

    public static void main(String[] args) {
        Observable observable = new Observable();
        observable.addObserver(notifier -> { notifier.removeObserver(this); });
        observable.change();
    }
}

Ich bekomme diesen Kompilierungsfehler:

Cannot use this in a static context and in a non `static` context



public class Main {
    public void main(String[] args) {
        method();
    }

    private void method() {
        Observable observable = new Observable();
        observable.addObserver(notifier -> {
                notifier.removeObserver(this);
        });
        observable.change();
    }
}

Der Kompilierungsfehler ist:

The method removeObserver(Main.Observer) in the type Main.Observable is not applicable for the arguments (Main)

Meine Frage lautet also: Gibt es eine Möglichkeit, auf das "Lambda-Objekt" zu verweisen this?

Pontard
quelle

Antworten:

110

Sie können thisin einem Lambda-Ausdruck nicht darauf verweisen . Die Semantik von thiswurde geändert, um nur auf die Instanz der umgebenden Klasse innerhalb des Lambda zu verweisen. Es gibt keine Möglichkeit, thisinnerhalb des Lambda auf die Lambda-Ausdrücke zu verweisen .

Das Problem ist, dass Sie thisin der main()Methode verwenden. Die Hauptmethode ist statisch und es gibt keinen Verweis auf ein Objekt, das darstellt this.

Wenn Sie thisinnerhalb einer Instanz einer inneren Klasse verwenden, verweisen Sie auf die Instanz der inneren Klasse. Ein Lambda-Ausdruck ist keine innere Klasse und thisbezieht sich nicht auf die Instanz des Lambda-Ausdrucks. Es bezieht sich auf die Instanz der Klasse, in der Sie den Lambda-Ausdruck definieren. In Ihrem Fall wäre es eine Instanz von Main. Da Sie sich jedoch in einer statischen Methode befinden, gibt es keine Instanz.

Dies sagt Ihnen Ihr zweiter Kompilierungsfehler. Sie übergeben eine Main-Instanz an Ihre Methode. Für Ihre Methodensignatur ist jedoch eine Instanz von Observer erforderlich.

Aktualisieren:

Die Java-Sprachspezifikation 15.27.2 lautet :

Im Gegensatz zu Code, der in anonymen Klassendeklarationen vorkommt, sind die Bedeutung von Namen und die in einem Lambda-Text angezeigten Schlüsselwörter this und super sowie die Zugänglichkeit von Deklarationen, auf die verwiesen wird, dieselben wie im umgebenden Kontext (mit der Ausnahme, dass Lambda-Parameter neue Namen einführen).

Die Transparenz dieser (explizit oder implizit) in den Körper eines Lambda - Ausdruck - das heißt, es ist das gleiche wie in dem umgebenden Rahmen der Behandlung - ermöglicht mehr Flexibilität für die Implementierungen und verhindert , dass die Bedeutung des nicht qualifizierten Namen im Körper aus abhängt auf Überlastauflösung.

In der Praxis ist es ungewöhnlich, dass ein Lambda-Ausdruck über sich selbst sprechen muss (entweder um sich selbst rekursiv aufzurufen oder seine anderen Methoden aufzurufen), während es üblicher ist, Namen zu verwenden, um auf Dinge in der umschließenden Klasse zu verweisen, die dies tun würden andernfalls beschattet werden ( dies, toString () ). Wenn ein Lambda-Ausdruck auf sich selbst verweisen muss (als ob dies der Fall wäre ), sollte stattdessen eine Methodenreferenz oder eine anonyme innere Klasse verwendet werden.

Gregor Koukkoullis
quelle
@gontard Ich kenne Java8 nicht, aber kannst du versuchen, statt zu machen observable finalund zu übergeben ? observablethis
BackSlash
@BackSlash Ja, ich weiß, ich kann diesen Fehler in dieser SSCCE umgehen. Aber ich suche nach allgemeineren Lösungen oder Erklärungen zu Lambdas.
Pontard
Vielen Dank für den Hinweis auf den Auszug aus dem JLS, es war genau das, wonach ich gesucht habe.
Pontard
@BackSlash willst du wie unten schreiben? Wie würde das funktionieren? final Observable observable = new Observable(); observable.addObserver(notifier -> { notifier.removeObserver(observable); });
Vikash
1
Ich bin ziemlich verwirrt, da es so aussieht, als
Zeile
1

Problemumgehung 1

Ihre change()Methode wirft ConcurrentModificationExceptiontrotzdem.

public class Main {
    public static void main(String[] args) {
        Observable observable = new Observable();
        final Observer[] a = new Observer[1];
        final Observer o = er -> er.removeObserver(a[0]); // !!
        a[0] = o;
        observable.addObserver(o);
        observable.change();
    }
}
public class Observable {
    private final java.util.Collection<Observer> n
        = java.util.new ArrayList<>();
    public void addObserver(Observer notifiable) {
        n.add(notifiable);
    }
    public void removeObserver(Observer notifiable) {
        n.add(notifiable);
    }
    public void change() {
        for (final Observer o : n.toArray(new Observer[n.size()])) {
            o.changed(this);
        }
    }
}
public interface Observer {
    void changed(Observable notifier);
}

Problemumgehung 2

Ich habe das geändert changed(Observable), changed(Observable, Observer)damit ein Beobachter mit sich selbst umgehen kann.

public class Main {
    public static void main(String[] args) {
        Observable observable = new Observable();
        final Observer o = (er, ee) -> er.removeObserver(ee); // !!
        observable.addObserver(o);
        observable.change();
    }
}
public class Observable {
    private final java.util.Collection<Observer> n
        = new java.util.ArrayList<>();
    public void addObserver(Observer notifiable) {
        n.add(notifiable);
    }
    public void removeObserver(Observer notifiable) {
        n.add(notifiable);
    }
    public void change() {
        for (final Observer o : n.toArray(new Observer[n.size()])) {
            o.changed(this, o);
        }
    }
}
public interface Observer {
    void changed(Observable notifier, Observer notifiee);
}
Jin Kwon
quelle
Ich habe eine andere Problemumgehung. Sie können default Observer getThis(){return this;}in Observer hinzufügen . Und verwenden getThis()statt this.
Dean Xu
1
@ DeanXu das funktioniert nicht. Der Lambda-Ausdruck kann nicht wie Ihre getThis()Methode auf Methoden der Schnittstelle zugreifen , genauso wie er nicht auf die thisInstanz der Klasse zugreifen kann, die die Schnittstelle implementiert.
Holger