2014-06-07 63 views
62

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

我最感兴趣的代码是

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/[email protected] 

得到类似

ClosureEqualsMain.a() 

甚至使用this.toString和方法。

my-ClosureEqualsMain.a(); 
+1

您可以在闭包中定义toString,equals和hashhCode方法。 –

+0

@AnkitZalani你可以给一个编译的例子吗? –

+0

@PeterLawrey,由于在Object中定义了toString,所以我认为你可以定义一个接口来提供'toString'的默认实现,而不会违反* single-method *接口的功能。我没有检查过这个。 –

回答

59

这个问题可以解释为相对于规范或实现。显然,实现可能会发生变化,但是如果发生这种情况,您可能会重新编写代码,因此我会在两者中进行回答。

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

从规范的角度来看,语言规范承诺只有评估(不调用)lambda表达式的结果是实现目标功能接口的类的实例。它对结果的身份或别名程度没有任何承诺。这是为了给予实现最大的灵活性以提供更好的性能(这就是lambda可以比内部类更快的速度;我们并不束缚于“必须创建唯一实例”约束,即内部类)

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

从实施的角度来看,您可以总结一点。目前,可能会改变实现lambdas的合成类与程序中的捕获站点之间的1:1关系。因此,捕获“x - > x + 1”的两个单独的代码位可能会映射到不同的类。但是,如果您在相同的捕获站点评估相同的lambda,并且lambda不捕获,则会得到相同的实例,可以将其与参考相等进行比较。

如果lambda表达式是序列化的,他们会更轻易放弃自己的状态,以换取牺牲一些性能和安全性(没有免费的午餐。)

一个领域是它可能是不切实际的调整定义平等是与方法引用,因为这将使他们能够被用作监听器并被正确注销。这正在考虑之中。

我认为你试图去的是什么:如果两个lambda表达式转换为相同的功能接口,是由相同的行为函数来表示,并具有相同的捕获指定参数时,它们是相同的

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

EG讨论了是否公开足够的信息来做出这些判断,以及讨论lambda是否应该实现更多选择等于/ hashCode或更多描述toString。结论是,我们不愿意支付任何性能成本来向调用者提供这些信息(坏的折衷,惩罚99.99%的用户获得0.01%的好处)。

关于toString的确切结论还没有达成,但还是留待未来重新审视。但是,在这个问题上双方都有一些很好的论点。这不是一个扣篮。

+0

+1虽然我明白支持'=='的平等是一个难以解决的问题,但我一直认为会有简单的情况,即编译器,如果不是JVM可以识别出在一行上的this :: a是在另一行上与'this :: a'相同。实际上,对于我来说,通过给每个呼叫站点实现它,对你而言都是不明显的。也许他们可以进行不同的优化,但我会认为内联可以做到这一点。 –

+0

在任何情况下,你都有一个绑定的对象和一个方法来调用,在这种简单的情况下,这对于平等来说已经足够了,但是我明白一般的解决方案很难,而不一致的行为确实会引起混淆,例如整数缓存意味着一些自动装箱的引用是==而其他的则需要equals()。 –

+1

与数组的'Array'和'Arrays'实用程序claases类似,因为它们无法获得像样的equals,hashCode或toString,所以我可以想象一天有'Closures'实用程序类。由于有些语言可以打印和排列并查看其内容,所以我想有些语言可以打印闭包,并了解闭包的作用。 (可能是一串代码更好,但对某些人来说不太令人满意) –

6

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

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

+0

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

5

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

相关问题