Есть ли способ сравнить лямбды?


скажем, у меня есть список объектов, которые были определены с помощью лямбда-выражения (укупорочных средств). Есть ли способ проверить их, чтобы их можно было сравнить?

код, который меня больше всего интересует

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

полный код

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

казалось бы, единственное решение-определить каждую лямбду как поле и использовать только эти поля. Если вы хотите распечатать вызванный метод, вам лучше использовать Method. Есть ли лучший способ с лямбда выражения лица?

кроме того, можно ли напечатать лямбду и получить что-то читаемое человеком? Если вы печатаете this::a вместо

ClosureEqualsMain$$Lambda/821270929@3f99bd52

сделать что-то вроде

ClosureEqualsMain.a()

или this.toString и метод.

my-ClosureEqualsMain.a();
3 68

3 ответа:

этот вопрос может быть интерпретирован относительно спецификации или реализации. Очевидно, что реализации могут измениться, но вы можете захотеть переписать свой код, когда это произойдет, поэтому я отвечу на оба.

Это также зависит от того, что вы хотите сделать. Вы хотите оптимизировать, или вы ищете железные гарантии того, что два экземпляра являются (или не являются) одной и той же функцией? (Если последнее, вы окажетесь в противоречии с вычислительной физикой, в этом даже такие простые проблемы, как вопрос о том, вычисляют ли две функции одно и то же, неразрешимы.)

с точки зрения спецификации спецификация языка обещает только то, что результат оценки (не вызова) лямбда-выражения является экземпляром класса, реализующего целевой функциональный интерфейс. Он не дает никаких обещаний относительно идентичности или степени сглаживания результата. Это по дизайну, чтобы дать реализации максимальную гибкость, чтобы предложить лучшую производительность (вот как лямбды могут быть быстрее, чем внутренние классы; мы не привязаны к ограничению "должен создать уникальный экземпляр", которым являются внутренние классы.)

таким образом, в принципе, спецификация не дает вам много, за исключением того, что два лямбда, которые равны ссылкам ( = = ), будут вычислять одну и ту же функцию.

с точки зрения реализации, можно сделать вывод, немного больше. Существует (в настоящее время, может измениться) отношение 1:1 между синтетическими классами, которые реализуют лямбды и сайты захвата в программе. Таким образом, два отдельных бита кода, которые захватывают "x -> x + 1", вполне могут быть сопоставлены с разными классами. Но если вы оцениваете одну и ту же лямбду на одном и том же сайте захвата, и эта лямбда не захватывается, вы получаете тот же экземпляр, который можно сравнить с равенством ссылок.

Если ваши лямбды сериализуемы, они легче откажутся от своего состояния, в обмен на жертву некоторой производительности и безопасности (нет бесплатного обеда.)

одна область, где может быть практично настроить определение равенства, - это ссылки на методы, потому что это позволит им использоваться в качестве слушателей и быть должным образом незарегистрированными. Это находится на рассмотрении.

Я думаю, что вы пытаетесь получить: если две лямбды преобразуются в один и тот же функциональный интерфейс, представлены одной и той же функцией поведения и имеют одинаковые захваченные args, они одинаковы

к сожалению это так трудно сделать (для несериализуемых лямбд вы не можете получить все компоненты этого) и недостаточно (потому что два отдельно скомпилированных файла могут конвертировать одну и ту же лямбду в один и тот же тип функционального интерфейса, и вы не сможете сказать.)

например, обсуждалось, следует ли предоставлять достаточно информации, чтобы иметь возможность делать эти суждения, а также обсуждать, должны ли лямбды реализовывать более селективные equals/hashCode или более описательные toString. Вывод был такой, что мы не были готовы платить ничего в стоимости производительности, чтобы сделать эту информацию доступной для вызывающего абонента (плохой компромисс, наказывающий 99,99% пользователей за то, что приносит пользу .01%).

окончательное заключение по toString не было достигнуто, но оставлено открытым для повторного рассмотрения в будущем. Тем не менее, там были некоторые хорошие аргументы с обеих сторон по этому вопросу; это не слэм-данк.

Я не вижу возможности, чтобы получить эти данные от самого закрытия. Закрытие не обеспечивает состояние.

но вы можете использовать Java-отражение, если вы хотите проверить и сравнить методы. Конечно, это не очень красивое решение, из-за производительности и исключений, которые нужно поймать. Но таким образом вы получаете эти метаинформации.

для сравнения labmdas я обычно позволяю интерфейсу расширяться Serializable а затем сравнить сериализованные байты. Не очень приятно, но работает в большинстве случаев.