Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does a lambda need to capture the enclosing instance when referencing a final String field?

This is based on this question. Consider this example where a method returns a Consumer based on a lambda expression:

public class TestClass {
    public static void main(String[] args) {
        MyClass m = new MyClass();
        Consumer<String> fn = m.getConsumer();

        System.out.println("Just to put a breakpoint");
    }
}

class MyClass {
    final String foo = "foo";

    public Consumer<String> getConsumer() {
        return bar -> System.out.println(bar + foo);
    }
}

As we know, it's not a good practice to reference a current state inside a lambda when doing functional programming, one reason is that the lambda would capture the enclosing instance, which will not be garbage collected until the lambda itself is out of scope.

However, in this specific scenario related to final strings, it seems the compiler could have just enclosed the constant (final) string foo (from the constant pool) in the returned lambda, instead of enclosing the whole MyClass instance as shown below while debugging (placing the breaking at the System.out.println). Does it have to do with the way lambdas are compiled to a special invokedynamic bytecode?

enter image description here

like image 890
M A Avatar asked Mar 23 '16 21:03

M A


People also ask

Why does lambda need a final variable?

Forcing the variable to be final avoids giving the impression that incrementing start inside the lambda could actually modify the start method parameter.

What is capture in lambda?

Capture clauseA 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.

Can lambda capture the instance and static variable in Java?

Thus, a lambda expression can obtain or set the value of an intrinsic or static variable and call a method define by its enclosing class. Lambda expression in java Using a local variable is as stated.

How resolve variable used in lambda expression should be final or effectively final?

The effectively final variables refer to local variables that are not declared final explicitly and can't be changed once initialized. A lambda expression can use a local variable in outer scopes only if they are effectively final.


2 Answers

In your code, bar + foo is really shorthand for bar + this.foo; we're just so used to the shorthand that we forget we are implicitly fetching an instance member. So your lambda is capturing this, not this.foo.

If your question is "could this feature have been implemented differently", the answer is "probably yes"; we could have made the specification/implementation of lambda capture arbitrarily more complicated in the aim of providing incrementally better performance for a variety of special cases, including this one.

Changing the specification so that we captured this.foo instead of this wouldn't change much in the way of performance; it would still be a capturing lambda, which is a much bigger cost consideration than the extra field dereference. So I don't see this as providing a real performance boost.

like image 98
Brian Goetz Avatar answered Sep 21 '22 16:09

Brian Goetz


If the lambda was capturing foo instead of this, you could in some cases get a different result. Consider the following example:

public class TestClass {
    public static void main(String[] args) {
        MyClass m = new MyClass();
        m.consumer.accept("bar2");
    }
}

class MyClass {
    final String foo;
    final Consumer<String> consumer;

    public MyClass() {
        consumer = getConsumer();
        // first call to illustrate the value that would have been captured
        consumer.accept("bar1");
        foo = "foo";
    }

    public Consumer<String> getConsumer() {
        return bar -> System.out.println(bar + foo);
    }
}

Output:

bar1null
bar2foo

If foo was captured by the lambda, it would be captured as null and the second call would print bar2null. However since the MyClass instance is captured, it prints the correct value.

Of course this is ugly code and a bit contrived, but in more complex, real-life code, such an issue could somewhat easily occur.

Note that the only true ugly thing, is that we are forcing a read of the to-be-assigned foo in the constructor, through the consumer. Building the consumer itself is not expected to read foo at that time, so it is still legit to build it before assigning foo – as long as you don't use it immediately.

However the compiler will not let you initialize the same consumer in the constructor before assigning foo – probably for the best :-)

like image 26
Didier L Avatar answered Sep 23 '22 16:09

Didier L