C’è un modo per confrontare i lambda?

Supponiamo di avere un elenco di oggetti che sono stati definiti utilizzando espressioni lambda (chiusure). C’è un modo per controllarli in modo che possano essere confrontati?

Il codice a cui sono più interessato è

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

Il codice completo è

 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 getStrategies() { return Arrays.asList(this::a, this::b, this::c); } private void testStrategies() { List 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  boolean equals(Closure c1, Closure c2) { // This doesn't compare the contents // like others immutables eg String return c1.equals(c2); } public static  int hashCode(Closure c) { return // a hashCode which can detect duplicates for a Set } public static  String asString(Closure c) { return // something better than Object.toString(); } } public String toString() { return "my-ClosureEqualsMain"; } } 

Sembrerebbe che l’unica soluzione sia definire ogni lambda come un campo e usare solo quei campi. Se si desidera stampare il metodo chiamato, è meglio usare Method . C’è un modo migliore con espressioni lambda?

Inoltre, è ansible stampare un lambda e ottenere qualcosa di leggibile dall’uomo? Se si stampa this::a anziché

 ClosureEqualsMain$$Lambda$1/821270929@3f99bd52 

ottenere qualcosa come

 ClosureEqualsMain.a() 

o persino usare this.toString e il metodo.

 my-ClosureEqualsMain.a(); 

Questa domanda potrebbe essere interpretata in relazione alla specifica o all’implementazione. Ovviamente, le implementazioni potrebbero cambiare, ma potresti essere disposto a riscrivere il tuo codice quando ciò accade, quindi risponderò ad entrambi.

Dipende anche da cosa vuoi fare. Stai cercando di ottimizzare, o stai cercando Ironclad garantisce che due istanze sono (o non sono) la stessa funzione? (In quest’ultimo caso, ti troverai in disaccordo con la fisica computazionale, in quanto anche problemi semplici come chiedere se due funzioni calcolano la stessa cosa sono indecidibili).

Dal punto di vista delle specifiche, le specifiche del linguaggio promettono solo che il risultato di valutare (non invocare) un’espressione lambda è un’istanza di una class che implementa l’interfaccia funzionale di destinazione. Non promette l’identity framework o il grado di aliasing del risultato. Questo è in base alla progettazione, per dare alle implementazioni la massima flessibilità per offrire prestazioni migliori (questo è il modo in cui lambdas può essere più veloce delle classi interne, non siamo vincolati al vincolo “deve creare istanze uniche” che sono le classi interne).

Quindi, in sostanza, le specifiche non ti danno molto, tranne ovviamente che due lambda che sono di riferimento uguali (==) stanno andando a calcolare la stessa funzione.

Da una prospettiva di implementazione, puoi concludere un po ‘di più. Esiste (attualmente, può cambiare) una relazione 1: 1 tra le classi sintetiche che implementano lambda e i siti di cattura nel programma. Quindi due distinti bit di codice che catturano “x -> x + 1” potrebbero essere mappati su classi diverse. Ma se si valuta lo stesso lambda nello stesso sito di cattura e che lambda non è in cattura, si ottiene la stessa istanza, che può essere confrontata con l’uguaglianza di riferimento.

Se i tuoi lambda sono serializzabili, abbandoneranno il loro stato più facilmente, in cambio di sacrificare alcune prestazioni e sicurezza (nessun pranzo gratis).

Un settore in cui potrebbe essere utile modificare la definizione di uguaglianza è con i riferimenti al metodo, perché ciò consentirebbe loro di essere utilizzati come ascoltatori e di essere correttamente non registrati. Questo è in esame.

Penso che quello che stai cercando di ottenere sia: se due lambda sono convertiti nella stessa interfaccia funzionale, sono rappresentati dalla stessa funzione di comportamento e hanno gli stessi argomenti catturati, sono uguali

Sfortunatamente questo è sia difficile da fare (per i lambda non serializzabili, non è ansible ottenere tutti i componenti di quello) e non abbastanza (perché due file compilati separatamente potrebbero convertire lo stesso lambda allo stesso tipo di interfaccia funzionale, e non si essere in grado di dirlo.)

L’EG ha discusso se esporre informazioni sufficienti per essere in grado di formulare questi giudizi, oltre a discutere se lambdas debba implementare più uguali selettivi / hashCode o più descrittivi aString. La conclusione è stata che non eravamo disposti a pagare nulla in termini di costo delle prestazioni per rendere queste informazioni disponibili al chiamante (compromesso negativo, punendo il 99,99% degli utenti per qualcosa che benefici dello 0,01%).

Una conclusione definitiva su toString non è stata raggiunta, ma è stata lasciata aperta per essere rivisitata in futuro. Tuttavia, ci sono stati alcuni argomenti validi su entrambe le parti su questo tema; questo non è uno schianto.

Non vedo una possibilità, per ottenere quelle informazioni dalla chiusura stessa. Le chiusure non forniscono lo stato.

Ma puoi usare Java-Reflection, se vuoi ispezionare e confrontare i metodi. Ovviamente non è una soluzione molto bella, a causa delle prestazioni e delle eccezioni, che sono da cogliere. Ma in questo modo ottieni quelle meta-informazioni.

Per confrontare i labmdas di solito l’interfaccia si estende Serializable e quindi confronta i byte serializzati. Non molto bello, ma funziona per la maggior parte dei casi.