有没有办法比较lambdas?

2022-08-31 11:33:23

假设我有一个对象列表,这些对象是使用lambda表达式(闭包)定义的。有没有办法检查它们以便进行比较?

我最感兴趣的代码是

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

似乎唯一的解决方案是将每个 lambda 定义为一个字段,并且只使用这些字段。如果要打印出调用的方法,最好使用 .有没有更好的 lambda 表达式方法?Method

另外,是否有可能打印lambda并获得人类可读的东西?如果您打印而不是this::a

ClosureEqualsMain$$Lambda$1/821270929@3f99bd52

得到类似的东西

ClosureEqualsMain.a()

甚至使用和方法。this.toString

my-ClosureEqualsMain.a();

答案 1

这个问题可以相对于规范或实现来解释。显然,实现可能会发生变化,但是当这种情况发生时,您可能愿意重写代码,因此我将回答这两个问题。

这也取决于你想做什么。您是否正在寻找优化,或者您是否正在寻找两个实例是(或不)相同功能的铁定保证?(如果是后者,你会发现自己与计算物理学不一致,因为即使是像问两个函数是否计算同一事物这样简单的问题也是不可判定的。

从规范的角度来看,语言规范仅承诺计算(不调用)lambda 表达式的结果是实现目标函数接口的类的实例。它对结果的标识或别名程度不作任何承诺。这是设计使实现具有最大灵活性以提供更好的性能(这就是lambdas比内部类更快的原因;我们不受内部类的“必须创建唯一实例”约束的约束。

所以基本上,规范并没有给你太多,除了显然两个引用相等(==)的lambda将计算相同的函数。

从实现的角度来看,您可以得出更多结论。实现 lambdas 的综合类与程序中的捕获站点之间存在 1:1 的关系(当前可能会更改)。因此,捕获“x -> x + 1”的两个单独的代码位很可能映射到不同的类。但是,如果您在同一捕获站点上评估相同的 lambda,并且该 lambda 未捕获,则您将获得相同的实例,该实例可以与引用相等性进行比较。

如果你的 lambda 是可序列化的,它们会更容易放弃状态,以换取牺牲一些性能和安全性(没有免费的午餐)。

调整相等性定义的一个可能可行的领域是使用方法引用,因为这将使它们能够用作侦听器并正确取消注册。这个问题正在考虑之中。

我认为你试图得到的是:如果两个lambda被转换为相同的功能接口,由相同的行为函数表示,并且具有相同的捕获参数,那么它们是相同的

不幸的是,这既很难做到(对于不可序列化的lambda,你无法获得其中的所有组件),也不够(因为两个单独编译的文件可以将相同的lambda转换为相同的功能接口类型,你无法分辨。

EG讨论了是否公开足够的信息以便能够做出这些判断,以及讨论lambda是否应该实现更具选择性/或更具描述性的toString。结论是,我们不愿意为将此信息提供给调用方而支付任何性能成本(糟糕的权衡,惩罚了99.99%的用户以获得0.01%的好处)。equalshashCode

没有就此得出明确的结论,但仍有待今后重新审议。然而,在这个问题上,双方都提出了一些很好的论点。这不是灌篮。toString


答案 2

为了比较labmdas,我通常让接口扩展,然后比较序列化的字节。不是很好,但适用于大多数情况。Serializable


推荐