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?
Forcing the variable to be final avoids giving the impression that incrementing start inside the lambda could actually modify the start method parameter.
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.
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.
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.
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.
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 :-)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With