25

If I obtain a method reference using the new syntax:

anObject::aMethod

Do I always get the same object? That is, can I trust that two references to the same method will be equal?

This is good to know if, for example, I plan to use them as Runnable callbacks that I can add and remove:

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

Would this require saving the reference in a Runnable variable to keep it stable?

Makoto
  • 104,088
  • 27
  • 192
  • 230
salezica
  • 74,081
  • 25
  • 105
  • 166
  • how is `removeCallback()` implemented ? – jmj Jun 07 '18 at 19:07
  • 4
    Lambdas don't have `equals` and `hashCode` - see [`LambdaMetafactory`](https://github.com/frohoff/jdk8u-jdk/blob/master/src/share/classes/java/lang/invoke/LambdaMetafactory.java). – Boris the Spider Jun 07 '18 at 19:32
  • 3
    [Is there a way to compare lambdas?](https://stackoverflow.com/questions/24095875/is-there-a-way-to-compare-lambdas/24098805#2409880) – Ousmane D. Jun 07 '18 at 19:47
  • 3
    [Two exact method references are not equal](https://stackoverflow.com/questions/28190304/two-exact-method-references-are-not-equal) – Ousmane D. Jun 07 '18 at 19:48
  • Incidentally, I do not think this is a duplicate. Method references use a syntax that doesn't allow capturing variables in a closure, except `this`. It's not clear that this follows the same rules as lambda expressions (which have every reason NOT to be stable references) – salezica Jun 08 '18 at 14:12

2 Answers2

19

JLS makes no promises about identity or equality of what you get out of method reference expressions.

You can run a quick test:

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      

But this is, of course, implementation dependent.

You could make your lambda Serializable and key your callback map with the serlialized representation. See How to serialize a lambda?. While this will work, it's not exactly required to work by the specs.

Misha
  • 27,433
  • 6
  • 62
  • 78
7

Just try this out to get the answer:

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

And the answer is... unfortunately not.

OF course if you keep the same reference (i.e. the same object), it will work; but if, as the example above, you request two lambdas, although they seem to be identical, they will never be equal. Therefore reference = object::methodand then later remove(reference) will obviously work, but remove(sameObject::sameMethod) from a collection will never work if it is written literaly as such.

The answer is also no for constructor (e.g. ArrayList::new) and unbound methods (e.g. Object::toString). It seems that a new lambda is constructed each time you use a lambda expression.

As @Hitobat points it out, this unequality makes sense if you think about what exactly are lambdas and where do they come from. Basicly, Supplier<String> x = myObject::toString is a syntactic suggar for Supplier<String> x = new Supplier<String>( ... ). Without a proper Object.equals overloading, two instances of an anonymous class are obviously different. As many people probably, I though that there was a kind of cache of frequently used lambdas somewhere to make it more efficient; well, not at all.

QuentinC
  • 12,311
  • 4
  • 24
  • 37
  • 2
    I guess this makes sense in retrospect. If you think of the method reference as just syntactic sugar for the longer anonymous class "new Supplier() { ... }" then without overloading `Object.equals` these anon instances won't match. – Hitobat Jun 07 '18 at 19:25
  • Exact. I will add a precision about this to my answer. – QuentinC Jun 07 '18 at 19:57
  • As explained in [Does a lambda expression create an object on the heap every time it's executed?](https://stackoverflow.com/q/27524445/2711488), the specification allows for object reusual and it does already happen under certain circumstances (i.e. non-capturing lambdas), but is left entirely at the discretion of the JVM. Since JVM implementations also have Escape Analysis which may elide object creation transparently (i.e. `supplier1 == supplier2` gets replaced by the constant `false` but the objects may not even exist), that’s reasonable. The performance matters, not the result of `x == y`… – Holger Jun 08 '18 at 10:36