35

I had some confusion about inner classes and lambda expression, and I tried to ask a question about that, but then another doubt arose, and It's probable better posting another question than commenting the previous one.

Straight to the point: I know (thank you Jon) that something like this won't compile

public class Main {
    public static void main(String[] args) {
        One one = new One();

        F f = new F(){      //1
            public void foo(){one.bar();}   //compilation error
        };

        one = new One();
    }
}

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

due to how Java manages closures, because one is not [effectively] final and so on.

But then, how come is this allowed?

public class Main {
    public static void main(String[] args) {
        One one = new One();

        F f = one::bar; //2

        one = new One();
    }
}

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

Is not //2 equivalent to //1? Am I not, in the second case, facing the risks of "working with an out-of-date variable"?

I mean, in the latter case, after one = new One(); is executed f still have an out of date copy of one (i.e. references the old object). Isn't this the kind of ambiguity we're trying to avoid?

Community
  • 1
  • 1
Luigi Cortese
  • 10,841
  • 6
  • 37
  • 48
  • 2
    Possible duplicate of [Why lambda expressions in Java 8 requires variables used inside it to use "final" modifier, but not when using method reference?](http://stackoverflow.com/questions/27609348/why-lambda-expressions-in-java-8-requires-variables-used-inside-it-to-use-final) – imz -- Ivan Zakharyaschev Oct 10 '15 at 19:18
  • But I like the accepted answer here more because it seems to be a bit more detailed and clear. – imz -- Ivan Zakharyaschev Oct 10 '15 at 19:19

4 Answers4

35

A method reference is not a lambda expression, although they can be used in the same way. I think that is what is causing the confusion. Below is a simplification of how Java works, it is not how it really works, but it is close enough.

Say we have a lambda expression:

Runnable f = () -> one.bar();

This is the equivalent of an anonymous class that implements Runnable:

Runnable f = new Runnable() {
    public void run() {
       one.bar();
    }
}

Here the same rules apply as for an anonymous class (or method local class). This means that one needs to effectively final for it to work.

On the other hand the method handle:

Runnable f = one::bar;

Is more like:

Runnable f = new MethodHandle(one, one.getClass().getMethod("bar"));

With MethodHandle being:

public class MethodHandle implements Runnable {
    private final Object object;
    private final Method method;

    public MethodHandle(Object object, java.lang.reflect.Method method) {
        this.object = Object;
        this.method = method;
    }

    @Override
    public void run() {
        method.invoke(object);
    }
}

In this case, the object assigned to one is assigned as part of the method handle created, so one itself doesn't need to be effectively final for this to work.

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
  • That's clear, thank you! So basically the point is that my second `f` is not a local class, but a reference to an object of some *elsewhere* defined type. So, the fact that `System.out.println(f.getClass());` outputs `class com.mui.cert.Main$$Lambda$1/321001045` does not necessarily mean that I'm dealing with a Lambda? – Luigi Cortese Oct 10 '15 at 11:03
  • 1
    @LuigiCortese The `$Lambda` is a bit of namespacing that the java compiler generates to handle lambdas and method handles; it doesn't mean both are lambdas. But as I said, the above is a simplification of how Java works. – Mark Rotteveel Oct 10 '15 at 11:06
  • Sorry if I repeat myself, just want to be sure I got it: is it correct ot say that my second `f` is not a local class reference, but a reference to an object of *somewhere else defined* type? – Luigi Cortese Oct 10 '15 at 11:08
  • 3
    @LuigiCortese No, it's a synthetic object created by the compiler, that references the object on which the method is to be invoked. – RealSkeptic Oct 10 '15 at 11:12
  • Ok, I'll go deeper in this by myself. Thanks for indicating the route =) – Luigi Cortese Oct 10 '15 at 11:13
  • @LuigiCortese You could look at the [Java Language Specification](http://docs.oracle.com/javase/specs/jls/se8/html/index.html), but especially on this subject I find it rather hard to read. – Mark Rotteveel Oct 10 '15 at 11:15
10

Your second example is simply not a lambda expression. It's a method reference. In this particular case, it chooses a method from a particular object, which is currently referenced by the variable one. But the reference is to the object, not to the variable one.

This is the same as the classical Java case:

One one = new One();
One two = one;
one = new One();

two.bar();

So what if one changed? two references the object that one used to be, and can access its method.

Your first example, on the other hand, is an anonymous class, which is a classical Java structure that can refer to local variables around it. The code refers to the actual variable one, not the object to which it refers. This is restricted for the reasons that Jon mentioned in the answer you referred to. Note that the change in Java 8 is merely that the variable has to be effectively final. That is, it still can't be changed after initialization. The compiler simply became sophisticated enough to determine which cases will not be confusing even when the final modifier is not explicitly used.

RealSkeptic
  • 33,993
  • 7
  • 53
  • 79
  • 1
    @There's something I'm missing (more than something). Citing to Jon's answer: "*When you create an instance of an anonymous inner class, any variables which are used within that class have their values copied in via the autogenerated constructor*". So, as I understand it, my inner class of type `F` when executing `one.bar()` is referring to its own copy (instance variable) of `one` reference. So, in both examples I'm referring to a given, specific object, because changing `one`'s value in `main` method cannot affect the inner class' copy. Am I right? – Luigi Cortese Oct 11 '15 at 16:33
  • 1
    Refer to the part where he mentions the confusion that could arise. The issue is that when you look at the code, you see references to `one` in it. These can be anything - even static references (though you will get warnings about that). You are referencing *one* itself in the code. If you use a debugger and get to that line, you'll see `one` being referenced. Not so in the case of taking the method reference. – RealSkeptic Oct 11 '15 at 17:26
  • Ok, so basically the point is that to allow local classes visibility on local variables, a "magic trick" is orchestrated to give you the illusion. Even though behind the magician's back local class copies values and works on the copies, to keep the illusion going a "coherent behaviour" must be granted, as if local class was really referencing variables, as if the illusion was real. Is that correct? – Luigi Cortese Oct 11 '15 at 17:37
  • 2
    I wouldn't resort to magic tricks, really. What I think is happening here is that in order not to be confusing, the JLS could say explicitly "Any local references in the closure are copies of the value as it was when control arrived to the line before the anonymous class definition". But they don't, because they may want to use full closures in the future, and don't want to tie themselves down. So in the next version of Java they could say "no more restrictions on local variables in closures" - all old code will still work, and new one will not be tied to old specs. – RealSkeptic Oct 11 '15 at 17:49
  • @RealSkeptic But surely they've burned that bridge with the way method references are implemented? – Paul Boddington Oct 11 '15 at 17:54
  • @PaulBoddington I think you're missing the original point which is the main part of my answer. Method references are *plain* references. When you debug code, and go to the part where you execute the method reference, you won't see any reference to *`one`* in the debugging session. The code will refer to **`this`**, not to `one`. – RealSkeptic Oct 11 '15 at 17:57
8

The consensus appears to be that this is because when you do it using an anonymous class, one refers to a variable, whereas when you do it using a method reference, the value of one is captured when the method handle is created. In fact, I think that in both cases one is a value rather than a variable. Let's consider anonymous classes, lambda expressions and method references in a bit more detail.

Anonymous classes

Consider the following example:

static Supplier<String> getStringSupplier() {
    final Object o = new Object();
    return new Supplier<String>() {
        @Override
        public String get() {
            return o.toString();
        }
    };
}

public static void main(String[] args) {
    Supplier<String> supplier = getStringSupplier();
    System.out.println(supplier.get());  // Use o after the getStringSupplier method returned.
}

In this example, we are calling toString on o after the method getStringSupplier has returned, so when it appears in the get method, o cannot refer to a local variable of the getStringSupplier method. In fact it is essentially equivalent to this:

static Supplier<String> getStringSupplier() {
    final Object o = new Object();
    return new StringSupplier(o);
}

private static class StringSupplier implements Supplier<String> {
    private final Object o;

    StringSupplier(Object o) {
        this.o = o;
    }

    @Override
    public String get() {
        return o.toString();
    }
} 

Anonymous classes make it look as if you are using local variables, when in fact the values of these variables are captured.

In contrast to this, if a method of an anonymous class references the fields of the enclosing instance, the values of these fields are not captured, and the instance of the anonymous class does not hold references to them; instead the anonymous class holds a reference to the enclosing instance and can access its fields (either directly or via synthetic accessors, depending on the visibility). One advantage is that an extra reference to just one object, rather than several, is required.

Lambda expressions

Lambda expressions also close over values, not variables. The reason given by Brian Goetz here is that

idioms like this:

int sum = 0;
list.forEach(e -> { sum += e.size(); }); // ERROR

are fundamentally serial; it is quite difficult to write lambda bodies like this that do not have race conditions. Unless we are willing to enforce -- preferably at compile time -- that such a function cannot escape its capturing thread, this feature may well cause more trouble than it solves.

Method references

The fact that method references capture the value of the variable when the method handle is created is easy to check.

For example, the following code prints "a" twice:

String s = "a";
Supplier<String> supplier = s::toString;
System.out.println(supplier.get());
s = "b";
System.out.println(supplier.get());

Summary

So in summary, lambda expressions and method references close over values, not variables. Anonymous classes also close over values in the case of local variables. In the case of fields, the situation is more complicated, but the behaviour is essentially the same as capturing the values because the fields must be effectively final.

In view of this, the question is, why do the rules that apply to anonymous classes and lambda expressions not apply to method references, i.e. why are you allowed to write o::toString when o is not effectively final? I do not know the answer to that, but it does seem to me to be an inconsistency. I guess it's because you can't do as much harm with a method reference; examples like the one quoted above for lambda expressions do not apply.

Paul Boddington
  • 37,127
  • 10
  • 65
  • 116
  • I agree, this is a little inconsistent; the rationale for the requirement of effective-final should apply to method reference too. It's difficult though to apply this requirement on method reference, since we have general `expr` in `expr::methodName`, it'd be silly to have a special rule when `expr` is a variable read. – ZhongYu Oct 10 '15 at 13:00
  • 1
    On the other hand, the rationale to require effective-final is pretty weak to begin with; if we drop the requirement for lambda/anon, I don't think it will actually cause confusions. – ZhongYu Oct 10 '15 at 13:00
  • @bayou.io Very good point about `expr::methodName`. I reckon that may be a large part of the reason. This is really one of those "why did the language designers do it this way?" questions, so answers are based largely on opinion. – Paul Boddington Oct 10 '15 at 13:10
  • @PaulBoddington I'm afraid I didn't get the distinction between *local variable* and *specific object*. `o` is actually both: a reference (*local variable*) "pointing" an object. I don't think that's the point of the question, or maybe I missed something about your answer – Luigi Cortese Oct 11 '15 at 15:51
  • @LuigiCortese `o` means two different things. The first time it is a local variable of the method `getStringSupplier`. The second time it is not. The second time it is an instance field of a separate class (shown by the second block of code). In other words the process is very similar to the process described in the accepted answer for a method reference. From that point of view I don't really understand why it has so many upvotes. However I don't claim to be an expert on this (I find the JLS impenetrable on this), and I could be wrong. That's why I decided to write a community wiki. – Paul Boddington Oct 11 '15 at 16:01
  • @LuigiCortese I'm considering putting a bounty on this question. I'm interested to know if the team behind Java 8 considered requiring that `o` has to be effectively final for you to be able to write `o::method`. If so I wonder what that pros and cons are and how they made the final decision. – Paul Boddington Oct 11 '15 at 16:09
  • @PaulBoddington to be honest, I've been trying to have a clear idea about this topic for a couple of days now – Luigi Cortese Oct 11 '15 at 16:11
  • @LuigiCortese Can you add the `java-8` tag to your question? I think the oracle guys search for that tag... – Paul Boddington Oct 11 '15 at 16:16
  • I came to your very same conclusion after spending some time thinking about it. But now, you say "*the restriction that the variables must be final is to avoid the obvious confusion*". My question is: "*is it really an obvious confusion? Do we really need this restriction*"? Being Java a pass-by-value language, I (the programmer) am definitely aware that a parameter passed to a funtion `foo()` may have changed in the calling method while `foo()` is running. Am I talking nonsense...? – Luigi Cortese Oct 11 '15 at 16:58
  • No, you're not talking nonsense. It's definitely debatable whether the restriction is necessary. – Paul Boddington Oct 11 '15 at 17:02
  • 1
    The advantage of the final restriction is, that the fields referenced from the outer type do not have to be copied to the closure. The implicite reference to the outer class is the only additional fields and can be used to dereference the other fields (directly or via synthethic accessors, depending on the visibility). See "[7 Variable capture](http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html)" from some explanation. – eckes Oct 11 '15 at 18:45
  • 1
    @eckes Thanks very much for that link. I've incorporated bits from it into my answer. I also cheekily used a sentence from your comment. I hope you don't mind - it's a community wiki anyway. Please feel free to edit the answer if you think it could be clearer/more accurate. – Paul Boddington Oct 13 '15 at 04:34
4

No. In your first example you define the implementation of F inline and try to access the instance variable one.

In the second example you basically define your lambda expression to be the call of bar() on the object one.

Now this might be a bit confusing. The benefit of this notation is that you can define a method (most of the time it is a static method or in a static context) once and then reference the same method from various lambda expressions:

msg -> System.out::println(msg);
hotzst
  • 7,238
  • 9
  • 41
  • 64
  • Still not very clear to me... in the second case after `one = new One();` `f` still have an old copy (i.e. references the old object). Isn't this the kind of ambiguity we're trying to avoid? – Luigi Cortese Oct 10 '15 at 10:49
  • A method reference is an handle to a method invocation on a specific instance not a general handle (unless it is a static method of course) to a method signature (like java.reflection.Method is). Therefore the handle does remeber the target object and does not need to have the variable beeing effectively final. – eckes Oct 11 '15 at 18:36
  • Your code snippet `msg -> System.out::println(msg);` is neither, method reference nor lambda expression. It’s an invalid mixture of both. – Holger Oct 12 '15 at 11:27