3

This code uses method reference to an instance method of a particular object:

public class Main {
    public static void main(String[] args) {
        One one=new One();
        // F f = ()->{one.bar();};   //previous wrong syntax
        F f = one::bar;              //4
        f.foo();
    }
}

class One{void bar(){}}
interface F{void foo();}

I know it works. But I'm not being able to understand why and how.

What I can't understand is how is it possible that F.foo() method is using a reference to an object that is not an argument to the method itself (signature is not void foo(One one)).

At line 4 I am

  1. creating an instance of a class that implements F interface
  2. implementing the method by using the reference one to invoke bar() method

But how can foo() have a scope on one reference? Am I being wrong trying to translate this solution to a "traditional, explicit implementation"? If not, what would it be the "explicit counterpart"?

Luigi Cortese
  • 10,841
  • 6
  • 37
  • 48
  • 1
    You don’t have a “reference to an instance method…” as you don’t have any method reference. Besides that, it’s unclear why you struggle to imagine the corresponding “traditional, explicit implementation” as inner classes also can access local variable of their surrounding context. – Holger Oct 09 '15 at 14:00
  • @Holger you're right, I just corrected the syntax. My confusion was probably wider than I thought: I knew about local classes visibility, but I totally missed the concept of "autogenerated constructor" (as explained [here](http://stackoverflow.com/questions/4732544/why-are-only-final-variables-accessible-in-anonymous-class)) and furthermore, when it comes to lambda expressions, something slightly different is happening behind the scenes (for example, you don't need local variables to be [effectively] final to be accessed) – Luigi Cortese Oct 09 '15 at 14:50
  • 1
    There is no difference between lambda expressions and inner classes regarding access to local variables. Both require local variables to be `final` or *effectively final*. This is different to method references. Method references of the form *expression::name* will evaluate the expression *immediately* and capture the result. Thus, the expression may also be a reference to a mutable variable as the immediate evaluation will result in its current value and capture that. In your example, `one` *is* effectively final, but even `F f = new One()::bar;` is valid as it captures the new object… – Holger Oct 09 '15 at 14:56
  • @Holger what do you mean with "*expression::name* will evaluate the expression *immediately* and capture the result"? – Luigi Cortese Oct 09 '15 at 15:03
  • 1
    As said, when you write `F f = new One()::bar;`, then `new One()` will get evaluated *immediately* and the result, an instance of `One`, gets bound to the method reference to `bar` when creating the object of type `F`. Subsequent invocations of `f.foo()` will always use that object of `One` that was captured when `F` was created. That’s totally different to `F f = () -> new One().bar();`, where a new `One` instance gets created *everytime* when `f.foo()` is invoked. – Holger Oct 09 '15 at 15:14
  • Ok, but I am surely missing something... you said that *Method references of the form expression::name will evaluate the expression immediately and capture the result*, this is why they can refer to mutable variables too. But then, how can this `F f = one::bar;` be different from this `F f = new F(){public void foo(){one.bar();}};`? Don't we have just one instance, only evaluated once? If so, why in the second case `one` needs to be *[effectively] final* to compile? Excuse me if my questions may sound stupid, I'm just trying really hard to understand all of this, thanks for your patience! – Luigi Cortese Oct 09 '15 at 15:26
  • In your second example (inner class), the code of method `foo()` gets re-evaluated every time `foo()` is invoked. This includes re-evaluation of `one` and thus, in case `one` is the name of a local variable of the surrounding context, it must be immutable to guarantee a consistent result, i.e. that capturing its value at construction time indeed evaluates to the same value, it will have when `foo()` is invoked. This is a conceptual difference, e.g `x -> System.out.println(x)` and `System.out::println` do not do the same, when you call `System.setOut(…)` in-between… – Holger Oct 11 '15 at 13:54

1 Answers1

6

Your lambda is compiled into private synthetic method which looks like this:

private static void lambda$1(One one) { one.bar(); }

At runtime the lambda expression is translated into runtime representation during the first time you execute this code. Current runtime representation in OpenJDK 8 is an anonymous class which takes the captured variables as constructor parameters and stores them into fields, then calls the lambda body method generated by compiler. Something like this is generated:

class lambda12345678 implements F {
    private One arg1;

    lambda12345678(One arg1) {this.arg1 = arg1;}

    public void foo() {
        lambda$1(arg1);
    }
}

And call site is technically replaced with constructor call, thus instead of

F f = ()->{one.bar();};

You effectively have

F f = new lambda12345678(one);

Note that if your lambda does not capture context, it works in more efficient way: only one object is created and reused. But in your example as lambda behavior depends on external state, every time the new instance is created.

Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
  • Great! This was the only possibililty I could think of, that a "hidden" instance variable was created for the subclass. Do you have any resource to link? – Luigi Cortese Oct 09 '15 at 12:33
  • 1
    @LuigiCortese, this behavior is not specified and may change in future. So the best information source is the JDK source code. [Here](http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/java/lang/invoke/InnerClassLambdaMetafactory.java#350) you can see how constructor is generated at runtime. See that PUTFIELD instructions added which actually save the arguments into fields. – Tagir Valeev Oct 09 '15 at 12:37
  • Thank you. One last thing, I didn't get the "note..." at the end of your answer. Could you explain further, please? – Luigi Cortese Oct 09 '15 at 12:40
  • 1
    @LuigiCortese, if your lambda does not capture variables (like `IntBinaryOperator lambda = (a, b) -> a+b`), upon the first invocation the instance of generated class is created, but upon the further invocations of this code the same instance is reused. Something like it's stored into the internal constant and your code replaced with `IntBinaryOperator lambda = CONSTANT_LAMBDA_OBJECT;`, not with `IntBinaryOperator lambda = new lambda12345678()` like in your case. – Tagir Valeev Oct 09 '15 at 12:55