有没有办法比较 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 执行角色?
有没有办法从 node.js 同步调用 AWS Lambda?