假设我有一个使用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();
答案 0 :(得分:72)
这个问题可以相对于规范或实现来解释。显然,实现可能会改变,但是你可能愿意在发生这种情况时重写你的代码,所以我会在两者上回答。
这还取决于你想做什么。您是在寻求优化,还是在寻找两个实例(或不是)相同功能的铁定保证? (如果是后者,你会发现自己与计算物理学不一致,因为即使是问两个函数是否计算相同的事情这样简单的问题也是不可判定的。)
从规范的角度来看,语言规范只承诺评估(不调用)lambda表达式的结果是实现目标功能接口的类的实例。它没有对结果的身份或别名程度做出任何承诺。这是设计,为实现提供最大的灵活性以提供更好的性能(这就是lambdas如何比内部类更快;我们不依赖于内部类的“必须创建唯一实例”约束。)
所以基本上,规范并没有给你太多,除了显然两个引用相等的lambda(==)将计算相同的函数。
从实施角度来看,您可以稍微总结一下。实现lambdas的合成类与程序中的捕获站点之间存在(当前可能会发生变化)1:1的关系。因此,捕获“x - > x + 1”的两个独立代码位可以很好地映射到不同的类。但是,如果您在同一个捕获站点评估相同的lambda,并且该lambda是非捕获的,那么您将获得相同的实例,可以将其与引用相等性进行比较。
如果你的lambdas是可序列化的,他们会更容易放弃他们的状态,以换取牺牲一些性能和安全性(没有免费的午餐。)
调整相等定义可能是切实可行的一个方面是使用方法引用,因为这样可以将它们用作侦听器并正确地取消注册。这是在考虑中。
我认为你想要达到的目的是:如果两个lambda被转换为相同的功能接口,由相同的行为函数表示,并且具有相同的捕获args,它们是相同的
不幸的是,这很难做到(对于不可序列化的lambda,你不能得到它的所有组件)并且不够(因为两个单独编译的文件可以将相同的lambda转换为相同的功能接口类型,而且你无法分辨。)
EG讨论了是否要公开足够的信息以便做出这些判断,以及讨论lambdas是否应该实现更具选择性的equals / hashCode或更具描述性的toString。结论是,我们不愿意支付任何性能成本,以便向呼叫者提供这些信息(糟糕的权衡,惩罚99.99%的用户获得的利益为.01%)。
未达到对toString的明确结论,但未来还有待重新审视。但是,双方在这个问题上都提出了一些很好的论点;这不是扣篮。
答案 1 :(得分:7)
为了比较labmdas,我通常让接口扩展implicit declaration of function ‘sched_setaffinity’
,然后比较序列化的字节。不是很好,但适用于大多数情况。
答案 2 :(得分:6)
我没有看到从封闭本身获取这些信息的可能性。 封闭物不提供状态。
但是如果要检查和比较方法,可以使用Java-Reflection。 当然,这不是一个非常漂亮的解决方案,因为它的性能和例外是可以捕获的。但是这样你就可以得到那些元信息。