Есть ли способ сравнить лямбды?
Скажем, у меня есть список объектов, которые были определены с помощью лямбда-выражений (замыканий). Есть ли способ проверить их, чтобы их можно было сравнить?
Код, который меня больше всего интересует,
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/821270929@3f99bd52
получить что-то вроде
ClosureEqualsMain.a()
или даже использовать this.toString
и метод.
my-ClosureEqualsMain.a();
3 ответа
Этот вопрос можно интерпретировать относительно спецификации или реализации. Очевидно, реализации могут измениться, но вы можете захотеть переписать свой код, когда это произойдет, поэтому я отвечу на оба вопроса.
Это также зависит от того, что вы хотите сделать. Вы ищете для оптимизации, или вы ищете железные гарантии того, что два экземпляра имеют (или не являются) одну и ту же функцию? (Если последнее, вы окажетесь в противоречии с вычислительной физикой, в которой даже такие простые проблемы, как вопрос о том, вычисляют ли две функции одно и то же, неразрешимы.)
С точки зрения спецификации языковая спецификация обещает только то, что результатом оценки (а не вызова) лямбда-выражения является экземпляр класса, реализующий целевой функциональный интерфейс. Он не дает никаких обещаний относительно идентичности или степени совмещения результатов. Это сделано для того, чтобы дать реализациям максимальную гибкость, чтобы предложить лучшую производительность (именно так лямбды могут быть быстрее, чем внутренние классы; мы не привязаны к ограничению "необходимо создать уникальный экземпляр", как для внутренних классов.)
В общем, спецификация не дает вам много, за исключением того, что две лямбда-выражения, равные ссылкам (==), будут вычислять одну и ту же функцию.
С точки зрения реализации, вы можете сделать вывод еще немного. Существует (в настоящее время может измениться) соотношение 1:1 между синтетическими классами, которые реализуют лямбды, и сайтами захвата в программе. Таким образом, два отдельных бита кода, которые захватывают "x -> x + 1", вполне могут быть сопоставлены с различными классами. Но если вы оцениваете одну и ту же лямбду на том же сайте захвата, и эта лямбда не захватывает, вы получаете тот же экземпляр, который можно сравнить с эталонным равенством.
Если ваши лямбды являются сериализуемыми, они с легкостью откажутся от своего состояния в обмен на потерю производительности и безопасности (без бесплатного обеда).
Одной из областей, где может оказаться целесообразным изменить определение равенства, является использование ссылок на методы, поскольку это позволит использовать их в качестве слушателей и должным образом не зарегистрировать. Это находится на рассмотрении.
Я думаю, что вы пытаетесь добраться до: если две лямбды преобразованы в один и тот же функциональный интерфейс, представлены одной и той же функцией поведения и имеют идентичные захваченные аргументы, они одинаковы
К сожалению, это сложно сделать (для не сериализуемых лямбд, вы не можете получить все компоненты этого) и недостаточно (потому что два отдельно скомпилированных файла могут преобразовать одну и ту же лямбду в один и тот же тип функционального интерфейса, и вы не не смогу сказать.)
ЭГ обсуждала, предоставлять ли достаточно информации, чтобы иметь возможность принимать эти решения, а также обсуждала, должны ли лямбда-выражения реализовывать более селективный код равенства /hashCode или более описательный toString. Был сделан вывод, что мы не готовы платить какую-либо плату за производительность, чтобы сделать эту информацию доступной для вызывающего абонента (плохой компромисс: наказание 99,99% пользователей за то, что приносит пользу 0,01%).
Окончательное заключение по ToString не было достигнуто, но оставлено открытым для повторного рассмотрения в будущем. Тем не менее, было несколько хороших аргументов с обеих сторон по этому вопросу; это не слэм-данк.
Для сравнения labmdas я обычно позволяю расширить интерфейс Serializable
и затем сравните сериализованные байты. Не очень приятно, но работает для большинства случаев.
Я не вижу возможности получить эту информацию от самого закрытия. Закрытие не обеспечивает состояние.
Но вы можете использовать Java-Reflection, если хотите проверить и сравнить методы. Конечно, это не очень красивое решение из-за производительности и исключений, которые нужно ловить. Но так вы получите метаинформацию.