Why aren't method references singleton?
Asked Answered
H

3

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
    }

}
Hepato answered 23/12, 2014 at 8:17 Comment(0)
H
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.

Hithermost answered 23/12, 2014 at 8:26 Comment(6)
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)Hepato
@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?Hithermost
It would appear that my edit was ninja'd because I accidentally pressed enter instead of shift-enter and submitted the comment early :PHepato
@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.Hithermost
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.Hepato
@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.Hithermost
M
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.

Middleoftheroader answered 23/12, 2014 at 8:50 Comment(5)
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.Hepato
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.Middleoftheroader
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.Hepato
@Hepato 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.Middleoftheroader
Ah, interesting, sorry, not particularly well versed in the behind the scenes part of it.Hepato
A
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.

Andromede answered 23/12, 2014 at 9:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.