Java方法引用是否稳定?

25

如果我使用新语法获取方法引用:

anObject::aMethod

我总是得到相同的对象吗?也就是说,我可以相信两个对同一方法的引用将是相等的吗?

如果我打算将它们用作可添加和删除的Runnable回调函数,那么了解这一点非常有用:

someLibrary.addCallback(anObject::aMethod)
// later
someLibrary.removeCallback(sameObject::sameMethod)

这是否需要将引用保存在一个 Runnable 变量中以保持其稳定性?


removeCallback() 的实现方式是什么? - jmj
4
Lambda表达式没有equalshashCode方法 - 请参阅 LambdaMetafactory - Boris the Spider
3
有没有一种方法可以比较Lambda表达式? - Ousmane D.
3
两个完全相同的方法引用并不相等。 - Ousmane D.
顺便说一下,我不认为这是重复的。方法引用使用的语法不允许在闭包中捕获变量,除了this。不清楚这是否遵循与lambda表达式相同的规则(它们没有理由成为稳定引用)。 - salezica
2个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
19

JLS 对於方法引用表达式的输出,其身份或相等性不做保证。

你可以运行一个快速测试:

Object obj = new Object();

IntSupplier foo = obj::hashCode;
IntSupplier bar = obj::hashCode;

System.out.println(foo == bar);  // false

System.out.println(foo.equals(bar));  // false      

但是这当然取决于具体的实现。

可以使你的lambda表达式Serializable并用序列化的表示方式作为回调映射的键。参见如何序列化lambda表达式?。虽然这样做可以起作用,但按规范来说并不是必须的。


7

只需尝试以下操作即可获得答案:

Object object = ...;
Supplier<String> s1 = object::toString;
Supplier<String> s2 = object::toString;
System.out.println(s1.equals(s2));

答案是……不幸的是,不能。

当然,如果你保持相同的引用(即相同的对象),它会起作用;但是,如上面的例子,如果您请求两个lambda,尽管它们看起来完全相同,它们永远不会相等。因此,reference = object::method,然后稍后使用remove(reference)显然有效,但是如果从集合中字面上写成remove(sameObject::sameMethod),它永远不会起作用。

对于构造函数(例如ArrayList::new)和未绑定方法(例如Object::toString),答案也是否定的。似乎每次使用lambda表达式时都会构造一个新的lambda。

正如@Hitobat所指出的那样,如果您考虑lambda的确切定义以及它们来自哪里,这种不平等是有意义的。基本上,Supplier<String> x = myObject::toStringSupplier<String> x = new Supplier<String>( ... )的语法糖。没有适当的Object.equals重载,匿名类的两个实例显然是不同的。就像许多人可能一样,我认为某个地方肯定有一个常用lambda的缓存,以使其更高效;好吧,完全没有。


2
回想起来,我猜这是有道理的。如果你把方法引用看作是更长的匿名类“new Supplier() { ... }”的语法糖,那么如果不重载Object.equals,这些匿名实例将无法匹配。 - Hitobat
准确。我会在我的答案中添加一个精度。 - QuentinC
正如在lambda表达式每次执行时是否会在堆上创建对象?中所解释的那样,规范允许对象重用,并且在某些情况下(即非捕获lambda)已经发生,但完全由JVM自行决定。由于JVM实现还具有逃逸分析,可以透明地省略对象创建(即supplier1 == supplier2被替换为常量false,但对象甚至可能不存在),这是合理的。性能很重要,而不是x == y的结果... - Holger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,