Gibt es eine Möglichkeit, Lambdas zu vergleichen?

Lesezeit: 7 Minuten

Gibt es eine Moglichkeit Lambdas zu vergleichen
Peter Lawrey

Angenommen, ich habe eine Liste von Objekten, die mit Lambda-Ausdrücken (Closures) definiert wurden. Gibt es eine Möglichkeit, sie zu überprüfen, damit sie verglichen werden können?

Der Code, der mich am meisten interessiert, ist

    List<Strategy> strategies = getStrategies();
    Strategy a = (Strategy) this::a;
    if (strategies.contains(a)) { // ...

Der vollständige Code ist

import java.util.Arrays;
import java.util.List;

public class ClosureEqualsMain {
    interface Strategy {
        void invoke(/*args*/);
        default boolean equals(Object o) { // doesn't compile
            return Closures.equals(this, o);
        }
    }

    public void a() { }
    public void b() { }
    public void c() { }

    public List<Strategy> getStrategies() {
        return Arrays.asList(this::a, this::b, this::c);
    }

    private void testStrategies() {
        List<Strategy> strategies = getStrategies();
        System.out.println(strategies);
        Strategy a = (Strategy) this::a;
        // prints false
        System.out.println("strategies.contains(this::a) is " + strategies.contains(a));
    }

    public static void main(String... ignored) {
        new ClosureEqualsMain().testStrategies();
    }

    enum Closures {;
        public static <Closure> boolean equals(Closure c1, Closure c2) {
            // This doesn't compare the contents 
            // like others immutables e.g. String
            return c1.equals(c2);
        }

        public static <Closure> int hashCode(Closure c) {
            return // a hashCode which can detect duplicates for a Set<Strategy>
        }

        public static <Closure> String asString(Closure c) {
            return // something better than Object.toString();
        }
    }    

    public String toString() {
        return "my-ClosureEqualsMain";
    }
}

Es scheint, dass die einzige Lösung darin besteht, jedes Lambda als Feld zu definieren und nur diese Felder zu verwenden. Wenn Sie die aufgerufene Methode ausdrucken möchten, verwenden Sie besser Method. Gibt es einen besseren Weg mit Lambda-Ausdrücken?

Ist es auch möglich, ein Lambda zu drucken und etwas Menschenlesbares zu erhalten? Wenn Sie drucken this::a anstatt

ClosureEqualsMain$$Lambda$1/821270929@3f99bd52

bekommen so etwas wie

ClosureEqualsMain.a()

oder sogar verwenden this.toString und die Methode.

my-ClosureEqualsMain.a();

  • Innerhalb der Closure können Sie die Methoden toString, equals und hashhCode definieren.

    – Ankit Zalani

    7. Juni 14 um 9:45 Uhr

  • @AnkitZalani Kannst du ein Beispiel geben, das kompiliert?

    – Peter Lawrey

    7. Juni 14 um 9:46 Uhr

  • @PeterLawrey, seit toString definiert ist Objectich denke, Sie können eine Schnittstelle definieren, die eine Standardimplementierung von bereitstellt toString ohne gegen die zu verstoßen Einzelmethode Voraussetzung für funktionsfähige Schnittstellen. Ich habe dies aber nicht überprüft.

    – Mike Samuel

    7. Juni 14 um 15:45 Uhr

  • @MikeSamuel Das ist falsch. Klassen erben keine in Schnittstellen deklarierten Standard-Objektmethoden; siehe stackoverflow.com/questions/24016962/… für eine Erklärung.

    – Brian Götz

    8. Juni 14 um 0:20 Uhr


  • @BrianGoetz, Danke für den Hinweis.

    – Mike Samuel

    8. Juni 14 um 1:55 Uhr

Gibt es eine Moglichkeit Lambdas zu vergleichen
Brian Götz

Diese Frage könnte relativ zur Spezifikation oder zur Implementierung interpretiert werden. Natürlich könnten sich Implementierungen ändern, aber Sie könnten bereit sein, Ihren Code neu zu schreiben, wenn das passiert, also werde ich bei beiden antworten.

Es hängt auch davon ab, was Sie tun möchten. Möchten Sie optimieren oder suchen Sie nach eiserner Garantie, dass zwei Instanzen die gleiche Funktion haben (oder nicht haben)? (Im letzteren Fall geraten Sie in Konflikt mit der Computerphysik, da selbst so einfache Probleme wie die Frage, ob zwei Funktionen dasselbe berechnen, unentscheidbar sind.)

Aus Spezifikationssicht verspricht die Sprachspezifikation nur, dass das Ergebnis der Auswertung (nicht des Aufrufs) eines Lambda-Ausdrucks eine Instanz einer Klasse ist, die die funktionale Zielschnittstelle implementiert. Es macht keine Zusagen über die Identität oder den Aliasing-Grad des Ergebnisses. Dies ist beabsichtigt, um Implementierungen maximale Flexibilität zu geben, um eine bessere Leistung zu bieten (auf diese Weise können Lambdas schneller sein als innere Klassen; wir sind nicht an die Einschränkung „Muss eindeutige Instanz erstellen“ gebunden, die innere Klassen sind.)

Im Grunde gibt Ihnen die Spezifikation also nicht viel, außer offensichtlich, dass zwei Lambdas, die referenzgleich (==) sind, dieselbe Funktion berechnen werden.

Aus Implementierungssicht können Sie etwas mehr schließen. Es besteht (derzeit, kann sich ändern) eine 1:1-Beziehung zwischen den synthetischen Klassen, die Lambdas implementieren, und den Capture-Sites im Programm. Zwei separate Codebits, die “x -> x + 1” erfassen, können also durchaus verschiedenen Klassen zugeordnet werden. Aber wenn Sie dasselbe Lambda an derselben Erfassungsstelle auswerten und dieses Lambda nicht erfassend ist, erhalten Sie dieselbe Instanz, die mit Referenzgleichheit verglichen werden kann.

Wenn Ihre Lambdas serialisierbar sind, geben sie ihren Zustand leichter auf, im Austausch dafür, dass sie etwas Leistung und Sicherheit opfern (kein kostenloses Mittagessen).

Ein Bereich, in dem es praktisch sein könnte, die Definition von Gleichheit zu optimieren, sind Methodenreferenzen, da dies ermöglichen würde, dass sie als Listener verwendet und ordnungsgemäß deregistriert werden. Dies wird derzeit geprüft.

Ich denke, was Sie erreichen wollen, ist: Wenn zwei Lambdas in dieselbe funktionale Schnittstelle konvertiert werden, durch dieselbe Verhaltensfunktion dargestellt werden und identische erfasste Argumente haben, sind sie gleich

Leider ist dies sowohl schwierig (für nicht serialisierbare Lambdas können Sie nicht alle Komponenten davon bekommen) als auch nicht genug (weil zwei separat kompilierte Dateien dasselbe Lambda in denselben funktionalen Schnittstellentyp konvertieren könnten, und Sie kann ich nicht sagen.)

Die EG diskutierte, ob genügend Informationen offengelegt werden sollten, um diese Urteile fällen zu können, und diskutierte, ob Lambdas selektiver implementiert werden sollten equals/hashCode oder beschreibender toString. Die Schlussfolgerung war, dass wir nicht bereit waren, irgendetwas an Leistungskosten zu zahlen, um diese Informationen dem Anrufer zur Verfügung zu stellen (schlechter Kompromiss, 99,99 % der Benutzer werden für etwas bestraft, das 0,01 % nutzt).

Ein definitives Fazit zu toString wurde nicht erreicht, sondern offen gelassen, um in Zukunft erneut besucht zu werden. Es gab jedoch einige gute Argumente auf beiden Seiten zu diesem Thema; das ist kein Slam-Dunk.

  • +1 Während ich die Unterstützung verstehe == Gleichheit ist im Allgemeinen ein schwer zu lösendes Problem. Ich hätte gedacht, dass es einfache Fälle geben würde, in denen der Compiler, wenn nicht die JVM, dies erkennen könnte this::a auf einer Zeile ist das gleiche wie this::a auf einer anderen Linie. Tatsächlich ist es mir immer noch nicht klar, was Sie gewinnen, wenn Sie jeder Aufrufseite eine eigene Implementierung geben. Vielleicht können sie anders optimiert werden, aber ich hätte gedacht, dass Inlining dies tun könnte.??

    – Peter Lawrey

    7. Juni 14 um 23:17 Uhr


  • Wir haben eine Reihe möglicher Implementierungen untersucht, darunter eine, bei der die Proxy-Klassen von mehreren Callsites gemeinsam genutzt wurden. Diejenige, die wir vorerst gewählt haben (ein großer Vorteil des „Metafabrik“-Ansatzes besteht darin, dass dies geändert werden kann, ohne die Benutzerklassendateien neu zu kompilieren), war die einfachste und leistungsfähigste. Wir werden die relative Leistung zwischen den Optionen weiterhin überwachen, während sich die VM weiterentwickelt, und wenn eine der anderen schneller ist, wechseln wir.

    – Brian Götz

    8. Juni 14 um 0:18 Uhr

  • Keine Änderungen für Java 9.

    – Brian Götz

    11. Februar 17 um 17:22 Uhr

  • @GeoffreyDeSmet Ich bin mir sicher, dass es mit guten Absichten gemacht wurde, aber es passiert so oft, dass Entwickler glauben, dass sie “keine Wahl” haben, wenn Leistung gegen Sicherheit oder Wartbarkeit gestellt wird. Dies wird, wie Sie andeuten, wahrscheinlich durch die Tatsache verstärkt, dass die Sicherheitsbedenken nicht so gut verstanden werden (obwohl es manchmal schwierig ist, die Leute dazu zu bringen, auf die anderen Bedenken zu hören, wenn es um die Leistung geht).

    – Brian Götz

    29. März 21 um 19:28 Uhr

  • @GeoffreyDeSmet Die fehlende Möglichkeit, Daten zu validieren, ist ein Problem, da Lambda-Ausdrücke und Methodenreferenzen jedoch hauptsächlich kapseln Verhalten, besteht das größere Problem darin, dass die Serialisierung den Zugriff auf dieses Verhalten für willkürlichen (nicht vertrauenswürdigen) Code ermöglicht. Diese Frage enthält ein praktisches Beispiel.

    – Holger

    14. April 21 um 7:25 Uhr

Um labmdas zu vergleichen lasse ich meistens das Interface erweitern Serializable und vergleichen Sie dann die serialisierten Bytes. Nicht sehr schön, aber funktioniert für die meisten Fälle.

  • Dasselbe gilt für hashCode von Lambdas, nicht wahr? Ich meine, ein Lambda in ein Byte-Array zu serialisieren (mit Hilfe von ByteArrayOutputStream und ObjectOutputStream) und es mit Arrays.hash (…) zu hashen.

    – mmirwaldt

    6. April 19 um 13:11 Uhr


Ich sehe keine Möglichkeit, diese Informationen aus der Schließung selbst zu bekommen. Die Schließungen bieten keinen Zustand.

Aber Sie können Java-Reflection verwenden, wenn Sie die Methoden überprüfen und vergleichen möchten. Das ist natürlich keine sehr schöne Lösung, wegen der Performance und der Ausnahmen, die es zu fangen gilt. Aber auf diese Weise erhalten Sie diese Metainformationen.

  • +1 Reflexion erlaubt mir zu bekommen this wie arg$1 aber nicht die aufgerufene Methode vergleichen. Ich muss möglicherweise den Bytecode lesen, um zu sehen, ob es dasselbe ist.

    – Peter Lawrey

    7. Juni 14 um 10:42 Uhr

.

758060cookie-checkGibt es eine Möglichkeit, Lambdas zu vergleichen?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy