Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lambda Expression and Variable Capture

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.

like image 817
Abu Bakkar Siddique Avatar asked Aug 28 '15 13:08

Abu Bakkar Siddique


People also ask

Can lambda capture the local variable in Java?

Lambda expression in java Using a local variable is as stated. However, when a lambda expression uses a local variable from its enclosing scope, a special situation is created that is referred to as a variable capture. In this case, a lambda expression may only use local variables that are effectively final.

Can we declare variable in lambda expression?

A lambda expression can't define any new scope as an anonymous inner class does, so we can't declare a local variable with the same which is already declared in the enclosing scope of a lambda expression. Inside lambda expression, we can't assign any value to some local variable declared outside the lambda expression.

How do you capture variables in lambda?

Capture clause A lambda can introduce new variables in its body (in C++14), and it can also access, or capture, variables from the surrounding scope. A lambda begins with the capture clause. It specifies which variables are captured, and whether the capture is by value or by reference.

What is variable capture?

Variable capture occurs when macroexpansion causes a name clash: when some symbol ends up referring to a variable from another context. Inadvertent variable capture can cause extremely subtle bugs. This chapter is about how to foresee and avoid them.


2 Answers

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.

like image 146
Alex Avatar answered Oct 10 '22 01:10

Alex


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

like image 34
Mithun Theertha Avatar answered Oct 09 '22 23:10

Mithun Theertha