Angenommen, ich habe eine Liste von Objekten, die mit Lambda-Ausdrücken (Abschlüssen) definiert wurden. Gibt es eine Möglichkeit, sie zu inspizieren, 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 lautet
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, sollten Sie sie besser verwenden Method
. Gibt es einen besseren Weg mit Lambda-Ausdrücken?
Ist es auch möglich, ein Lambda zu drucken und etwas menschlich Lesbares zu bekommen? Wenn Sie drucken , this::a
statt
ClosureEqualsMain$$Lambda$1/821270929@3f99bd52
so etwas bekommen
ClosureEqualsMain.a()
oder sogar verwenden this.toString
und die Methode.
my-ClosureEqualsMain.a();
toString
definiert istObject
, können Sie eine Schnittstelle definieren, die eine Standardimplementierung von bietet,toString
ohne die Anforderung einer einzelnen Methode zu verletzen, damit Schnittstellen funktionsfähig sind. Ich habe das allerdings nicht überprüft.Antworten:
Diese Frage könnte in Bezug auf die Spezifikation oder die Implementierung interpretiert werden. Natürlich könnten sich die Implementierungen ändern, aber Sie könnten bereit sein, Ihren Code in diesem Fall neu zu schreiben, also werde ich auf beide antworten.
Es hängt auch davon ab, was Sie tun möchten. Möchten Sie optimieren oder suchen Sie nach Garantien, dass zwei Instanzen dieselbe Funktion haben (oder nicht)? (Wenn letzteres der Fall ist, werden Sie im Widerspruch zur Computerphysik stehen, da selbst Probleme, die so einfach sind wie die Frage, ob zwei Funktionen dasselbe berechnen, unentscheidbar sind.)
Aus Sicht der Spezifikation 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 gibt keine Zusagen über die Identität oder den Grad des Aliasing des Ergebnisses. Dies ist beabsichtigt, um Implementierungen maximale Flexibilität zu bieten, um eine bessere Leistung zu bieten (auf diese Weise können Lambdas schneller sein als innere Klassen; wir sind nicht an die Einschränkung gebunden, dass innere Klassen eine eindeutige Instanz erstellen müssen).
Im Grunde genommen gibt Ihnen die Spezifikation nicht viel, außer dass zwei Lambdas, die referenzgleich (==) sind, dieselbe Funktion berechnen werden.
Aus Sicht der Implementierung können Sie etwas mehr schließen. Zwischen den synthetischen Klassen, die Lambdas implementieren, und den Erfassungsorten im Programm besteht (kann sich derzeit ändern) eine 1: 1-Beziehung. Daher können zwei separate Codebits, die "x -> x + 1" erfassen, durchaus verschiedenen Klassen zugeordnet werden. Wenn Sie jedoch dasselbe Lambda an derselben Erfassungsstelle auswerten und dieses Lambda nicht erfasst, erhalten Sie dieselbe Instanz, die mit der Referenzgleichheit verglichen werden kann.
Wenn Ihre Lambdas serialisierbar sind, geben sie ihren Zustand leichter auf, als Gegenleistung für Leistungseinbußen und Sicherheit (kein kostenloses Mittagessen).
Ein Bereich, in dem es möglicherweise sinnvoll ist, die Definition der Gleichheit zu optimieren, sind Methodenreferenzen, da diese es ihnen ermöglichen würden, als Zuhörer verwendet zu werden und ordnungsgemäß nicht registriert zu werden. Dies wird derzeit geprüft.
Ich denke, Sie versuchen zu erreichen: Wenn zwei Lambdas in dieselbe Funktionsschnittstelle 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 überhaupt nicht alle Komponenten davon erhalten) als auch nicht ausreichend (da zwei separat kompilierte Dateien dasselbe Lambda in denselben funktionalen Schnittstellentyp konvertieren könnten, und Sie würde es nicht sagen können.)
Die EG erörterte, ob genügend Informationen veröffentlicht werden sollten, um diese Urteile fällen zu können, und erörterte, ob Lambdas selektiver
equals
/hashCode
oder beschreibender für String implementieren sollten . Die Schlussfolgerung war, dass wir nicht bereit waren, Leistungskosten zu zahlen, um diese Informationen dem Anrufer zur Verfügung zu stellen (schlechter Kompromiss, der 99,99% der Benutzer für etwas bestraft, das 0,01% zugute kommt).Eine endgültige Schlussfolgerung
toString
wurde nicht erreicht, sondern offen gelassen, um in Zukunft erneut geprüft zu werden. Zu diesem Thema wurden jedoch auf beiden Seiten einige gute Argumente vorgebracht. Dies ist kein Slam-Dunk.quelle
==
Gleichstellung im Allgemeinen ein schwer zu lösendes Problem ist, hätte ich gedacht, dass es einfache Fälle geben würde, in denen der Compiler, wenn nicht die JVM erkennen könnte, dassthis::a
in einer Zeile dasselbe ist wiethis::a
in einer anderen Zeile. Tatsächlich ist mir immer noch nicht klar, was Sie gewinnen, wenn Sie jeder Anrufstelle eine eigene Implementierung geben. Vielleicht können sie anders optimiert werden, aber ich hätte gedacht, dass Inlining dies tun könnte.Array
undArrays
Utility-Klauseln für Arrays, da sie keine anständigen Equals, HashCode oder toString erhalten konnten, kann ich mirClosures
eines Tages eine Utility-Klasse vorstellen . Da es Sprachen gibt, in denen Sie drucken und anordnen und deren Inhalt anzeigen können, stelle ich mir vor, dass es Sprachen gibt, in denen Sie Verschlüsse drucken und einen Einblick in die Funktionsweise des Verschlusses erhalten können. (Möglicherweise ist eine Zeichenfolge des Codes besser, aber für einige unbefriedigend)MethodHandle
in der zugrunde liegenden binären Schnittstelle verwendeten s kanonisch sind . Die Meta-Factory kann also nicht erkennen, ob zwei Referenzen auf dieselbe Methode abzielen, indem sie nur die Referenzen vergleicht. Es musste eine eingehendere Analyse durchführen, wenn es versuchte, dieselbe Implementierung für äquivalente Methodenreferenzen zurückzugeben.Um labmdas zu vergleichen, lasse ich normalerweise die Schnittstelle erweitern
Serializable
und vergleiche dann die serialisierten Bytes. Nicht sehr schön, funktioniert aber in den meisten Fällen.quelle
Ich sehe keine Möglichkeit, diese Informationen aus der Schließung selbst zu erhalten. Die Verschlüsse liefern keinen Zustand.
Sie können jedoch Java-Reflection verwenden, wenn Sie die Methoden überprüfen und vergleichen möchten. Natürlich ist das keine sehr schöne Lösung, wegen der Leistung und der Ausnahmen, die zu fangen sind. Aber auf diese Weise erhalten Sie diese Meta-Informationen.
quelle
this
,arg$1
aber nicht zu vergleichen. Möglicherweise muss ich den Bytecode lesen, um festzustellen, ob er identisch ist.