Lambda Expression and Variable Capture
Asked Answered
C

2

16

Please explain to me how a lambda expression can use and modify instance variables of its enclosing class, but can only use local variables of its enclosing scope. (Unless it is final or effective final?)

My basic question is how instance variables of a class are modifiable from within a lambda and local variables are not, in the context of scope.

Cattier answered 28/8, 2015 at 13:53 Comment(5)
Since lambda's are effectively an anonymous inner class, this is a possible duplicate of Why are only final variables accessible in anonymous class?Euxenite
This is true for Java 8, but as far as I know it is not specified, so this could be changed in future versions of Java.Erlandson
only this is captured. all access to an instance variable within the lambda body is done through this.field, therefore you can write to it too.Drive
@Erlandson - I think it is specified and it won't change in future. see these comments.Drive
@bayou.io - While I'm pretty sure I read a blog post or something a while ago which stated Lambdas at the moment are compiled to Anonymous Inner Classes, but could that change for performance reasons in the future, I'm not able to find this article. So maybe you're right - even if it was not specified, at the moment its effectively the same.Erlandson
E
15

At first, we can take a look at the JLS, which states the following:

Any local variable, formal parameter, or exception parameter used but not declared in a lambda expression must either be declared final or be effectively final (§4.12.4), or a compile-time error occurs where the use is attempted.

Any local variable used but not declared in a lambda body must be definitely assigned (§16 (Definite Assignment)) before the lambda body, or a compile-time error occurs.

Similar rules on variable use apply in the body of an inner class (§8.1.3). The restriction to effectively final variables prohibits access to dynamically-changing local variables, whose capture would likely introduce concurrency problems. Compared to the final restriction, it reduces the clerical burden on programmers.

The restriction to effectively final variables includes standard loop variables, but not enhanced-for loop variables, which are treated as distinct for each iteration of the loop (§14.14.2).


To understand it better, take a look at this example class:

public class LambdaTest {

    public static void main(String[] args) {
        LambdaTest test = new LambdaTest();
        test.returnConsumer().accept("Hello");
        test.returnConsumerWithInstanceVariable().accept("Hello");
        test.returnConsumerWithLocalFinalVariable().accept("Hello");
    }

    String string = " world!";

    Consumer<String> returnConsumer() {
        return ((s) -> {System.out.println(s);});
    }

    Consumer<String> returnConsumerWithInstanceVariable() {
        return ((s) -> {System.out.println(s + string);});
    }

    Consumer<String> returnConsumerWithLocalFinalVariable() {
        final String foo = " you there!";
        return ((s) -> {System.out.println(s + foo);});
    }

}

Output of main is

Hello
Hello world!
Hello you there!

This is because returning a lambda here is much the same as creating a new anonymous class with new Consumer<String>() {...}. Your lambda - an instance of Consumer<String> has a reference to the class it has been created in. You could rewrite the returnConsumerWithInstanceVariable() to use System.out.println(s + LambdaTest.this.string), this would do exactly the same. This is why you are allowed to to access (and modify) instance variables.

If you have a (effectively) final local variable in your method, you can access it because it gets copied to your lambda instance.

But, however, if its not final, what do you think should happen in the following context:

Consumer<String> returnConsumerBad() {
    String foo = " you there!";
    Consumer<String> results = ((s) -> {System.out.println(s + foo);});
    foo = " to all of you!";
    return results;
}

Should the value get copied to your instance, but then not get updated when the local variable is updated? That would probably cause confusion, as I think many programmers would expected foo to have the new value "to all of you" after returning this lambda.

If you had a primitive value, it would lay on the stack. So you could not simply reference the local variable, because it could disappear after the end of your method is reached.

Erlandson answered 28/8, 2015 at 14:17 Comment(0)
M
2

You can refer this article - https://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood explained on lambda expressions compilation. As explained lambda expressions/blocks of code are compiled into the anonymous class, these anonymous class are compiled with the name format (<<Enclosing Class name>>$<<1(Number)>>), so suppose assume if non final local variables are allowed then compiler cannot trace it from where this local variable is referred as anonymous class' '.class' files are created/compiled with aforementioned format separately like normal java classes.

So if local variable is final then compiler creates a final instance in the anoymous class which doesn't create ambiguity to compiler. Refer the link mention above for more information

Mclaurin answered 1/3, 2018 at 15:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.