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();
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.
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.
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.
.
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 istObject
ich denke, Sie können eine Schnittstelle definieren, die eine Standardimplementierung von bereitstellttoString
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