I have got the following code, which is somewhat abstracted from a real implementation I had in a Java program:
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
String line;
while ((line = bufferedReader.readLine()) != null) {
String lineReference = line;
runLater(() -> consumeString(lineReference));
}
Here I need to use a reference copy for the lambda expression, when I try to use line
I get:
Local variables referenced from a lambda expression must be final or effectively final
It seems rather awkward to me, as all I do to fix it is obtain a new reference to the object, this is something the compiler could also figure out by itself.
So I would say line
is effectively final here, as it only gets the assignment in the loop and nowhere else.
Could anyone shed some more light on this and explain why exactly it is needed here and why the compile cannot fix it?
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.
A lambda expression can access a variable of it's enclosing scope. A Lambda expression has access to both instance and static variables of it's enclosing class and also it can access local variables which are effectively final or final.
We can declare a variable as a final string[] array and able to access that array index within a lambda expression.
So I would say
line
is effectively final here, as it only gets the assignment in the loop and nowhere else.
No, it's not final because during the variable's lifetime it is getting assigned a new value on every loop iteration. This is the complete opposite of final.
I get: 'Local variables referenced from a lambda expression must be final or effectively final'. It seems rather awkward to me.
Consider this: You're passing the lambda to runLater(...)
. When the lambda finally executes, which value of line
should it use? The value it had when the lambda was created, or the value it had when the lambda executed?
The rule is that lambdas (appear to) use the current value at time of lambda execution. They do not (appear to) create a copy of the variable. Now, how is this rule implemented in practice?
If line
is a static field, it's easy because there is no state for the lambda to capture. The lambda can read the current value of the field whenever it needs to, just as any other code can.
If line
is an instance field, that's also fairly easy. The lambda can capture the reference to the object in a private hidden field in each lambda object, and access the line
field through that.
If line
is a local variable within a method (as it is in your example), this is suddenly not easy. At an implementation level, the lambda expression is in a completely different method, and there is no easy way for outside code to share access to the variable which only exists within the one method.
To enable access to the local variable, the compiler would have to box the variable into some hidden, mutable holder object (such as a 1-element array) so that the holder object could be referenced from both the enclosing method and the lambda, giving them both access to the variable within.
Although that solution would technically work, the behavior it achieves would be undesirable for a bundle of reasons. Allocating the holder object would give local variables an unnatural performance characteristic which would not be obvious from reading the code. (Merely defining a lambda that used a local variable would make the variable slower throughout the method.) Worse than that, it would introduce subtle race conditions into otherwise simple code, depending on when the lambda is executed. In your example, by the time the lambda executes, any number of loop iterations could have happened, or the method might have returned, so the line
variable could have any value or no defined value, and almost certainly wouldn't have the value you wanted. So in practice you'd still need the separate, unchanging lineReference
variable! The only difference is that the compiler wouldn't require you do to that, so it would allow you to write broken code. Since the lambda could ultimately execute on a different thread, this would also introduce subtle concurrency and thread visibility complexity to local variables, which would require the language to allow the volatile
modifier on local variables, and other bother.
So, for the lambda to see the current changing values of local variables would introduce a lot of fuss (and no advantages since you can do the mutable holder trick manually if you ever need to). Instead, the language says no to the whole kerfuffle by simply demanding that the variable be final
(or effectively final). That way, the lambda can capture the value of the local variable at lambda creation time, and it doesn't need to worry about detecting changes because it knows there can't be any.
This is something the compiler could also figure out by itself
It did figure it out, which is why it disallows it. The lineReference
variable is of absolutely no benefit to the compiler, which could easily capture the current value of line
for use in the lambda at each lambda object's creation time. But since the lambda wouldn't detect changes to the variable (which would be impractical and undesirable for the reasons explained above), the subtle difference between capture of fields and capture of locals would be confusing. The "final or effectively final" rule is for the programmer's benefit: it prevents you from wondering why changes to a variable don't appear within a lambda, by preventing you from changing them at all. Here's an example of what would happen without that rule:
String field = "A";
void foo() {
String local = "A";
Runnable r = () -> System.out.println(field + local);
field = "B";
local = "B";
r.run(); // output: "BA"
}
That confusion goes away if any local variables referenced within the lambda are (effectively) final.
In your code, lineReference
is effectively final. Its value is assigned exactly once during its lifetime, before it goes out of scope at the end of each loop iteration, which is why you can use it in the lambda.
There is an alternative arrangement of your loop possible by declaring line
inside the loop body:
for (;;) {
String line = bufferedReader.readLine();
if (line == null) break;
runLater(() -> consumeString(line));
}
This is allowed because line
now goes out of scope at the end of each loop iteration. Each iteration effectively has a fresh variable, assigned exactly once. (However, at a low level the variable is still stored in the same CPU register, so it's not like it has to be repeatedly "created" and "destroyed". What I mean is, there is happily no extra cost to declaring variables inside a loop like this, so it's fine.)
Note: All this is not unique to lambdas. It also applies identically to any classes declared lexically inside the method, from which lambdas inherited the rules.
Note 2: It could be argued that lambdas would be simpler if they followed the rule of always capturing the values of variables they use at lambda creation time. Then there would be no difference in behavior between fields and locals, and no need for the "final or effectively final" rule because it would be well-established that lambdas don't see changes made after lambda creation time. But this rule would have its own uglinesses. As one example, for an instance field x
accessed within a lambda, there would be a difference between the behavior of reading x
(capturing final value of x
) and this.x
(capturing final value of this
, seeing its field x
changing). Language design is hard.
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