Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java method reference resolving [duplicate]

I'm trying to understand how method references work in java. At first sight it is pretty straightforward. But not when it comes to such things:

There is a method in Foo class:

public class Foo {
    public Foo merge(Foo another) {
        //some logic
    }
}

And in another class Bar there is a method like this:

public class Bar {
    public void function(BiFunction<Foo, Foo, Foo> biFunction) {
       //some logic
    }
}

And a method reference is used:

new Bar().function(Foo::merge);

It complies and works, but I don't understand how does it match this:

Foo merge(Foo another)

to BiFunction method:

R apply(T t, U u);

???

like image 376
Kirill Bazarov Avatar asked Apr 13 '18 13:04

Kirill Bazarov


1 Answers

There is an implicit this argument on instance methods. This is defined §3.7 of the JVM specification:

The invocation is set up by first pushing a reference to the current instance, this, on to the operand stack. The method invocation's arguments, int values 12 and 13, are then pushed. When the frame for the addTwo method is created, the arguments passed to the method become the initial values of the new frame's local variables. That is, the reference for this and the two arguments, pushed onto the operand stack by the invoker, will become the initial values of local variables 0, 1, and 2 of the invoked method.

To understand why method invocation is done this way, we need to understand how the JVM stores code in memory. The code and the data of an object are separated. In fact, all methods of one class (static and non-static) are stored in the same place, the method area (§2.5.4 of JVM spec). This allows to store each method only once instead of re-storing them for each instance of a class over and over again. When a method like

someObject.doSomethingWith(someOtherObject);

is called, it gets actually compiled to something that looks more like

doSomething(someObject, someOtherObject);

Most Java-programmers would agree that someObject.doSomethingWith(someOtherObject) has a "lower cognitive complexity": we do something with someObject that involves someOtherObject. The center of this action is someObject, where someOtherObject is just a means to an end.

With doSomethingWith(someObject, someOtherObject), you do not transport this semantics of someObject being the center of the action.

So in essence, we write the first version, but the computer prefers the second version.

As was pointed out by @FedericoPeraltaSchaffner, you can even write the implicit this parameter explicitly since Java 8. The exact definition is given in JLS, §8.4.1:

The receiver parameter is an optional syntactic device for an instance method or an inner class's constructor. For an instance method, the receiver parameter represents the object for which the method is invoked. For an inner class's constructor, the receiver parameter represents the immediately enclosing instance of the newly constructed object. Either way, the receiver parameter exists solely to allow the type of the represented object to be denoted in source code, so that the type may be annotated. The receiver parameter is not a formal parameter; more precisely, it is not a declaration of any kind of variable (§4.12.3), it is never bound to any value passed as an argument in a method invocation expression or qualified class instance creation expression, and it has no effect whatsoever at run time.

The receiver parameter must be of the type of the class and must be named this.

This means that

public String doSomethingWith(SomeOtherClass other) { ... }

and

public String doSomethingWith(SomeClass this, SomeOtherClass other) { ... }

will have the same semantic meaning, but the latter allows for e.g. annotations.

like image 163
Turing85 Avatar answered Oct 13 '22 12:10

Turing85