Czy można porównać lambdy?

Powiedzmy, że mam listę obiektów, które zostały zdefiniowane za pomocą wyrażeń lambda (zamknięć). Czy istnieje sposób, aby je sprawdzić, aby można je było porównać?

Najbardziej interesuje mnie kod

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

Pełny kod to

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

Wydaje się, że jedynym rozwiązaniem jest zdefiniowanie każdego lambda jako pola i użycie tylko tych pól. Jeśli chcesz wydrukować wywołaną metodę, lepiej Użyj Method. Czy jest lepszy sposób z wyrażeniami lambda?

Również, czy to można wydrukować lambda i uzyskać coś czytelnego dla człowieka? Jeśli drukujesz this::a zamiast

ClosureEqualsMain$$Lambda$1/821270929@3f99bd52

Get something like

ClosureEqualsMain.a()

Lub nawet użyć this.toString i metody.

my-ClosureEqualsMain.a();
Author: Stuart Marks, 2014-06-07

3 answers

To pytanie może być interpretowane w odniesieniu do specyfikacji lub implementacji. Oczywiście implementacje mogą się zmienić, ale możesz być skłonny przepisać swój kod, kiedy to się stanie, więc odpowiem na obu.

To również zależy od tego, co chcesz zrobić. Czy chcesz zoptymalizować, czy też szukasz gwarancji, że dwa instancje są (lub nie są) tą samą funkcją? (Jeśli to drugie, to znajdziesz się w sprzeczności z fizyką obliczeniową, w tym nawet problemy tak proste, jak pytanie, czy dwie funkcje obliczają to samo, są nie do podjęcia decyzji.)

Z punktu widzenia specyfikacji, specyfikacja języka obiecuje tylko, że wynikiem oceny (nie wywołania) wyrażenia lambda jest instancja klasy implementującej docelowy interfejs funkcjonalny. Nie daje żadnych obietnic co do tożsamości lub stopnia aliasingu wyniku. Jest to z założenia, aby dać implementacjom maksymalną elastyczność, aby zaoferować lepszą wydajność (w ten sposób lambda mogą być szybsze niż klasy wewnętrzne; nie jesteśmy przywiązani do ograniczenia "must create unique instance", którym są klasy wewnętrzne.)

Więc zasadniczo, specyfikacja nie daje wiele, z wyjątkiem oczywiście, że dwie lambdy, które są równe referencjom ( = = ), będą obliczać tę samą funkcję.

Z perspektywy implementacji można wnioskować nieco więcej. Istnieje (obecnie może ulec zmianie) zależność 1:1 pomiędzy klasami syntetycznymi, które implementują lambda, a przechwytywaniem miejsca w programie. Tak więc dwa oddzielne bity kodu, które przechwytują "x - > x + 1", mogą być odwzorowane na różne klasy. Ale jeśli oceniasz tę samą lambda w tym samym miejscu przechwytywania i że lambda nie jest przechwytywana, otrzymujesz tę samą instancję, którą można porównać z równością odniesienia.

Jeśli Twoje lambdy są serializowalne, łatwiej zrezygnują ze swojego stanu, w zamian za poświęcenie wydajności i bezpieczeństwa (bez darmowego lunchu.)

Jeden obszar, w którym może być praktyczne do poprawienia definicji równości są odniesienia do metod, ponieważ umożliwiłoby to ich użycie jako słuchaczy i odpowiednie niezarejestrowanie. Jest to rozważane.

Myślę, że to, co próbujesz dostać się do jest: jeśli dwa lambda są przekonwertowane do tego samego interfejsu funkcjonalnego, są reprezentowane przez tę samą funkcję zachowania i mają identyczne przechwycone args, są takie same

Niestety jest to zarówno trudne do zrobienia (dla nie serializowalnych lambd, nie można w ogóle składników tego) i za mało (bo dwa osobno skompilowane pliki mogłyby przekonwertować ten sam lambda na ten sam typ interfejsu funkcjonalnego, a ty nie byłabyś w stanie tego stwierdzić.)

EG dyskutował, czy ujawnić wystarczająco dużo informacji, aby móc dokonać tych osądów, a także dyskutował, czy lambda powinien zaimplementować bardziej selektywne equals / hashCode lub bardziej opisowe toString. Wniosek był taki, że nie jesteśmy skłonni zapłacić nic w kosztach wykonania, aby informacje te są dostępne dla rozmówcy (zły tradeoff, karze 99,99% użytkowników za coś, co przynosi korzyści .01%).

Ostateczny wniosek na temat toString nie został osiągnięty, ale pozostawiono otwarte do ponownego rozpatrzenia w przyszłości. Jednak obie strony wysunęły dobre argumenty w tej sprawie; nie jest to slam-dunk.

 66
Author: Brian Goetz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-07 17:37:34

Nie widzę możliwości, aby uzyskać te informacje z samego zamknięcia. Zamknięcia nie zapewniają stanu.

Ale możesz użyć Java-Reflection, jeśli chcesz sprawdzić i porównać metody. Oczywiście nie jest to zbyt piękne rozwiązanie, ze względu na wydajność i wyjątki, które są do złapania. Ale w ten sposób zdobędziesz te meta-informacje.

 6
Author: F. Böller,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-07 09:54:43

Aby porównać labmdy zazwyczaj pozwalam interfejsowi rozszerzyć Serializable, a następnie porównać serializowane bajty. Nie bardzo ładne, ale działa w większości przypadków.

 5
Author: KIC,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-22 12:50:10