问题
I can't think why the captured variables are final or effectively final in lambda expressions. I looked over this question and really quite didn't get the answer.
What is this variable capture?
As I searched solutions for my problem, I read that these variables are final because of concurrency problems. But for such situation why can't we lock the task code in the lambda with a reentrant lock
object.
public class Lambda {
private int instance=0;
public void m(int i,String s,Integer integer,Employee employee) {
ActionListener actionListener = (event) -> {
System.out.println(i);
System.out.println(s);
System.out.println(integer);
System.out.println(employee.getI());
this.instance++;
employee.setI(4);
integer++;//error
s="fghj";//error
i++;//error
};
}
}
In this particular code I want know the reasons why the last three statements gives an error, and why do we get to mutate Employee
since it's a local variable.(Employee is just a class with getters and setters ofint i
.)
Also i like to know why we can mutate this.instance
too.
I appreciate a full detailed answer on all facts I mentioned above.
回答1:
I read that these variables are final because of concurrency problems.
Wrong, this has nothing to do with concurrency, it's all about how lambdas (and anonymous classes) "capture" variable values.
I want know the reasons why the last three statements gives an error
Because they are captures, so they must be effectively final.
You really don't need to know why the internals require this, just accept the fact that you need to adhere to that rule.
i like to know why we can mutate
this.instance
Because the code doesn't capture instance
, it captures this
, and this
is implicitly final.
Reason Why
A lambda is mostly syntactic sugar for an anonymous class. That's not really true, but for the purpose of this explanation, it's true enough, and the explanation is easier to understand for an anonymous class.
First understand, there is no such thing as an anonymous class in the JVM. Actually, there is no such thing as a lambda expression either, but that's a different story.
However, since Java (the language) has anonymous classes, but the JVM doesn't, the compiler has to fake it, by converting the anonymous class into an inner class. (FYI: Inner classes don't exist in the JVM either, so the compiler has to fake that too.)
Let's do this by example. Say we have this code:
// As anonymous class
int i = 0;
Runnable run = new Runnable() {
@Override
public void run() {
System.out.println(i);
}
}
// As lambda expression:
int i = 0;
Runnable run = () -> System.out.println(i);
For the anonymous class, the compiler will generate a class like this:
final class Anon_1 implements Runnable {
private final int i;
Anon_1(int i) {
this.i = i;
}
@Override
public void run() {
System.out.println(i);
}
}
and then compile the code to:
int i = 0;
Runnable run = new Anon_1(i);
That's how capture works, by copying the value of the "captured" variable.
The variable isn't captured at all, the value is, because Java is pass-by-value in the constructor call.
Now you can argue, that there is no reason why i
should be effectively final. Sure, the local variable i
and the field i
are now separate, but they could be separately modified.
But there is a reason, and it's a really good reason. The fact that i
has been copied, and is separate, is entire hidden, and is an implementation detail. Programmers would constantly forget that, and think they are the same, which would lead to lots of failed code, and many wasted hours of debugging to be reminded of that.
For code clarity, it must be as-if the i
local variable was captured, and that the i
in the anonymous class is the same as the i
outside, because that is what the Java language defines it to be, even though the JVM can't do that.
To make it appear like that, the local variable MUST be effectively final, so the fact that (internally) the variable wasn't captured at all, makes no difference to the running code.
来源:https://stackoverflow.com/questions/61075400/variable-capture-in-lambda