有没有办法比较 lambdas?

Posted

技术标签:

【中文标题】有没有办法比较 lambdas?【英文标题】:Is there a way to compare lambdas? 【发布时间】:2014-07-28 13:16:10 【问题描述】:

假设我有一个使用 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 定义为一个字段并仅使用这些字段。如果你想打印出调用的方法,你最好使用Method。有没有更好的 lambda 表达式方法?

另外,是否可以打印 lambda 并获得人类可读的内容?如果您打印this::a 而不是

ClosureEqualsMain$$Lambda$1/821270929@3f99bd52

得到类似的东西

ClosureEqualsMain.a()

甚至使用this.toString和方法。

my-ClosureEqualsMain.a();

【问题讨论】:

您可以在闭包中定义toString、equals和hashhCode方法。 @AnkitZalani 你能举一个编译的例子吗? @PeterLawrey,由于toString 是在Object 上定义的,我认为您可以定义一个接口,提供toString 的默认实现而不违反单一方法 接口功能的要求。不过我还没有检查过。 @MikeSamuel 这是不正确的。类不继承接口中声明的默认 Object 方法;请参阅***.com/questions/24016962/… 了解说明。 @BrianGoetz,感谢您的指点。 【参考方案1】:

这个问题可以相对于规范或实现来解释。显然,实现可能会发生变化,但你可能愿意在发生这种情况时重写你的代码,所以我会同时回答。

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

从规范的角度来看,语言规范只承诺评估(而不是调用)lambda 表达式的结果是实现目标功能接口的类的实例。它不承诺结果的身份或混叠程度。这是设计使然,为实现提供最大的灵活性以提供更好的性能(这就是 lambda 比内部类更快的原因;我们不受内部类的“必须创建唯一实例”约束。)

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

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

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

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

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

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

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

尚未就toString 得出明确结论,但留待未来重新审视。但是,在这个问题上,双方都提出了一些很好的论据;这不是灌篮。

【讨论】:

+1 虽然我理解支持== 相等性通常是一个很难解决的问题,但我认为编译器会在一些简单的情况下,如果不是JVM 可以识别this::a on一行与另一行的this::a 相同。事实上,通过为每个呼叫站点提供自己的实现,对我来说仍然不是很明显。也许它们可以进行不同的优化,但我认为内联可以做到这一点。?? 我们研究了许多可能的实现,其中包括在调用站点之间共享代理类的一种。我们现在使用的那个(“元工厂”方法的一大好处是可以在不重新编译用户类文件的情况下进行更改)是最简单且性能最好的。随着 VM 的发展,我们将继续监控选项之间的相对性能,当其他选项之一更快时,我们将切换。 Java 9 没有变化。 @GeoffreyDeSmet 我确信这样做是出于好意,但这种情况经常发生,以至于当性能与安全性或可维护性相冲突时,开发人员认为他们“别无选择”。正如您所建议的那样,这可能会因为安全问题未被充分理解这一事实而放大(尽管在性能方面,有时很难让人们倾听其他问题。) @GeoffreyDeSmet 缺少验证数据的可能性是一个问题,但由于 lambda 表达式和方法引用主要封装 行为,更大的问题是序列化允许访问它任意(不受信任的代码)的行为。 This question 包含一个实际示例。【参考方案2】:

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

【讨论】:

这同样适用于 lambda 的 hashCode,不是吗?我的意思是将 lambda 序列化为字节数组(在 ByteArrayOutputStream 和 ObjectOutputStream 的帮助下)并通过 Arrays.hash(...) 对其进行散列。【参考方案3】:

我看不到从闭包本身获取这些信息的可能性。 闭包不提供状态。

但是如果你想检查和比较方法,你可以使用 Java-Reflection。 当然,这不是一个非常漂亮的解决方案,因为要捕获的性能和异常。但是这样你就可以得到那些元信息。

【讨论】:

+1 反射允许我将this 作为arg$1 但不比较调用的方法。我可能需要读取字节码,看看是否相同。

以上是关于有没有办法比较 lambdas?的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法使用 cloudformation 创建 aws lambda 执行角色?

有没有办法规避 lambda 表达式的类型化?

有没有办法从 node.js 同步调用 AWS Lambda?

有没有办法用来自特定用户的推文触发 AWS lambda 函数

有没有办法在 lambda 表达式中显式定义泛型参数类型?

有没有办法在 lambda 表达式树中使用“动态”?