22

In Java, the following code returns false on both queries. Why? Wouldn't it be simpler for method references to be singleton? It would certainly make attaching and detaching listeners a lot simpler. As it is you need to keep a constant for any method reference that will need to be equivalence checked, you can't just use the method reference operator at every necessary location.

public class Main {

    public Main() {
        // TODO Auto-generated constructor stub
    }

    public void doStuff() {

    }

    public static void main(String[] args) {
        Main main = new Main();
        Runnable thing1 = main::doStuff;
        Runnable thing2 = main::doStuff;
        System.out.println(thing1 == thing2); // false
        System.out.println(thing1.equals(thing2)); // false
    }

}
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
Dimitriye98
  • 207
  • 2
  • 10

3 Answers3

16

For instance methods, I don't think it would make sense for them to be cached. You'd have to cache one method per instance... which would either mean an extra field within the class associated with the method - one per public method, presumably, because the methods could be referenced from outside the class - or cached within the user of the method reference, in which case you'd need some sort of per-instance cache.

I think it makes more sense for method references to static methods to be cached, because they'll be the same forever. However, to cache the actual Runnable, you'd need a cache per type that it was targeting. For example:

public interface NotRunnable {
    void foo();
}

Runnable thing1 = Main::doStuff; // After making doStuff static
NotRunnable thing2 = Main::doStuff;

Should thing1 and thing2 be equal here? How would the compiler know what type to create, if so? It could create two instances here and cache them separately - and always cache at the point of use rather than the point of method declaration. (Your example has the same class declaring the method and referencing it, which is a very special case. You should consider the more general case where they're different>)

The JLS allows for method references to be cached. From section 15.13.3:

Next, either a new instance of a class with the properties below is allocated and initialized, or an existing instance of a class with the properties below is referenced.

... but even for static methods, it seems javac doesn't do any caching at the moment.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • So why not override .equals() for referenced methods? All that would be required would be each one holding a reference to `main` and `Main.class.getMethod("doStuff")` (Thanks for the tip @CostiCiudatu) – Dimitriye98 Dec 23 '14 at 08:41
  • @Dimitriye98: That's definitely a possibility - but you'd need to define the semantics precisely. For example, are references to instance methods on two distinct but equal objects equal? Maybe... but any time you relied on `equals` like this you'd be relying on an implementation detail of the function - what if someone implemented `Runnable` manually instead? – Jon Skeet Dec 23 '14 at 08:43
  • It would appear that my edit was ninja'd because I accidentally pressed enter instead of shift-enter and submitted the comment early :P – Dimitriye98 Dec 23 '14 at 08:44
  • @Dimitriye98: Right, so would your equality check call `equals` on the target as well? Basically I can see some uses for this, but there are lots of situations to think about. – Jon Skeet Dec 23 '14 at 08:45
  • Isn't equivalence always an implementation detail? If I define a class Fraction with the constructor `Fraction(int numerator, int denominator)` it would make sense for me to say that `new Fraction(1, 2).equals(new Fraction(2, 4))`, even if the internally stored values might not be the same. – Dimitriye98 Dec 23 '14 at 08:50
  • @Dimitriye98: Well sort of - but I think in this case the JLS would want to specify how it was defined. Again, I'm not saying it's a bad idea by any means - just that it would need a bit of careful consideration. – Jon Skeet Dec 23 '14 at 08:58
4

In this simple case, you could do as you suggest by creating extra static or instance fields as appropriate, more complex if the lambda refers to objects, however the intent is to inline these instances out of existence e.g.

List<String> list = ....
list.stream().filter(s -> !s.isEmpty()).forEach(System.out::println);

should be as efficient (even creating no more objects than)

for (String s : list)
    if(!s.isEmpty())
         System.out.println(s);

It can eliminate the objects by inlining the stream code and using escape analysis to remove the need to create objects in the first place.

For this reason there has been little focus on implementing equals(), hashCode() or toString(), accessing them via reflection for closures. AFAIK, this deliberate to avoid the objects being using in ways not intended.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • Is the inlining compile-time? If so it shouldn't be a problem to check if the lambda / method reference will be assigned to anything. – Dimitriye98 Dec 23 '14 at 08:56
  • The problem is not whether it sia ssigned to anything, but whether it "escapes" the method i.e. if not it can be optimised away and notionally added to the stack instead of the heap, provided you don't use methods like equals etc. – Peter Lawrey Dec 23 '14 at 09:02
  • Either way, isn't just extra work for the compiler to do? Shouldn't change performance much assuming the compiler can properly recognize when to inline and when not to. – Dimitriye98 Dec 23 '14 at 09:09
  • @Dimitriye98 The `javac` does almost no optimisation. The JIT does all the optimisation at runtime which mean the JVM can be changed and how the code is inlined or used can be changed in say Java 9 or 10 without you having to recompile your code. – Peter Lawrey Dec 23 '14 at 09:11
  • Ah, interesting, sorry, not particularly well versed in the behind the scenes part of it. – Dimitriye98 Dec 23 '14 at 09:15
2

Making methods singletons will require to synchronize acquiring a reference to them. This gives a big overhead for such a simple operation with unpredictable result. The other solution is to create object for each method on class loading, but this results in many redundant objects, because only few method require referencing. I think synchronization is the main issue.

Mikhail
  • 4,175
  • 15
  • 31