2014-06-07 10 views
62

Dire che ho un elenco di oggetti che sono stati definiti utilizzando espressioni lambda (chiusure). C'è un modo per controllarli in modo che possano essere confrontati?C'è un modo per confrontare i lambda?

Il codice mi interessa di più è

List<Strategy> 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<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"; 
    } 
} 

sembrerebbe l'unica soluzione è quella di definire ogni lambda come un campo e utilizzare solo quei campi. Se si desidera stampare il metodo chiamato, è meglio usare Method. C'è un modo migliore con espressioni lambda?

Inoltre, è possibile stampare un lambda e ottenere qualcosa di leggibile dall'uomo? Se si stampa this::a invece di

ClosureEqualsMain$$Lambda$1/[email protected] 

ottenere qualcosa di simile

ClosureEqualsMain.a() 

o anche utilizzare this.toString e il metodo.

my-ClosureEqualsMain.a(); 
+1

È possibile definire i metodi toString, equals e hashhCode all'interno della chiusura. –

+0

@AnkitZalani Puoi fare un esempio che compila? –

+0

@PeterLawrey, Poiché 'toString' è definito su' Object', penso che sia possibile definire un'interfaccia che fornisce un'implementazione predefinita di 'toString' senza violare il requisito * a metodo singolo * per le interfacce funzionali. Non ho controllato questo però. –

risposta

59

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 si vuole 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 della lingua promettono solo che il risultato di valutare (non invocare) un'espressione lambda è un'istanza di una classe che implementa l'interfaccia funzionale di destinazione. Non promette l'identità 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 le classi interne sono.)

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, è possibile 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 lambda sono serializzabili, vi daranno il loro stato più facile, in cambio di sacrificare alcune prestazioni e la sicurezza (pranzo libero).

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

Penso che quello che si sta cercando di raggiungere è: se due lambda vengono convertite alla stessa interfaccia funzionale, sono rappresentati dalla stessa funzione comportamento, e hanno identici args catturati, sono la stessa

Sfortunatamente questo è sia difficile da fare (per i lambda non serializzabili, non è possibile 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 tali sentenze, nonché discutere se i lambda debbano implementare più selettivi equivale a/hashCode o più toString descrittivo. 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 rivista in futuro. Tuttavia, ci sono stati alcuni argomenti validi su entrambe le parti su questo tema; questo non è uno schianto.

+0

+1 Mentre capisco che il supporto di '==' l'uguaglianza è un problema difficile da risolvere in generale, avrei pensato che ci sarebbero stati casi semplici in cui il compilatore, se non la JVM poteva riconoscere che 'this :: a' su una riga è lo stesso di 'this :: a' su un'altra riga. In effetti non è ancora chiaro per me cosa si ottiene dando a ogni sito di chiamata la propria implementazione. Forse possono essere ottimizzati in modo diverso, ma avrei pensato che l'inline avrebbe potuto farlo. ?? –

+0

In ogni caso, hai un oggetto associato e un metodo da chiamare e questo sarebbe sufficiente per l'uguaglianza in questo semplice caso, ma capisco che una soluzione generale è difficile, e il comportamento incoerente causa confusione, ad es. La cache dei numeri interi significa che alcuni riferimenti autoboxed sono == e altri richiedono equals(). –

+1

Come le claune di utilità 'Array' e' Arrays' per gli array che non potevano ottenere un numero decente uguale, hashCode o toString, posso immaginare una classe di utilità 'Closures' un giorno. Poiché ci sono linguaggi in cui è possibile stampare e allineare e vedere il suo contenuto, immagino ci siano lingue in cui è possibile stampare chiusure e ottenere informazioni su cosa fa la chiusura. (Probabilmente una stringa del codice è migliore ma insoddisfacente per alcuni) –

6

Non vedo la possibilità di ottenere tali informazioni dalla chiusura stessa. Le chiusure non forniscono lo stato.

Ma è possibile utilizzare Java-Reflection, se si desidera 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.

+0

+1 reflection mi consente di ottenere 'this' come' arg $ 1' ma non confrontare il metodo chiamato. Potrei aver bisogno di leggere il codice byte per vedere se è lo stesso. –

5

Per confrontare 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.

Problemi correlati