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

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

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

    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$1/[email protected]

получить что-то вроде

ClosureEqualsMain.a()

или даже использовать this.toString и метод.

my-ClosureEqualsMain.a();

Ответ 1

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

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

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

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

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

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

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

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

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

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

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

Ответ 2

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

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

Ответ 3

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