We all know you can't do things like this:
int a = 7;
new Runnable() {
public void run() {
System.out.println(a);
}
}.run();
...
...without making a final. I get the technical reason why and it's because local variables live on the stack and you can't safely make a copy unless you know it won't change.
What I struggle to see however is why the compiler doesn't have an implementation hack so that it when it sees the above situation it compiles down to something like:
int[] a = {7};
new Runnable() {
public void run() {
System.out.println(a[0]);
}
}.run();
...
Then we're in the position where it's safe to access a from an anonymous inner class and indeed change it if we wish. Of course, it might only do this hack when we actually change a. As far as I could see this would be a relatively simple thing to put in, would work for all types and would allow a to be changed from whatever context. Of course, the above proposal could be changed to use synthetic wrapper classes for multiple values or another approach that's a bit more efficient, but the idea is the same. I guess there's a small performance hit, but I doubt it'd be excessive especially with the potential for more optimisations under the hood. Aside from perhaps certain reflective calls that rely on synthetic fields being a certain way breaking, I can't see many disadvantages, but I've never heard it seriously proposed! Is there a reason why?
解决方案
When the anonymous inner class is constructed, the values of all the variables used within it are copied. So if the inner class then tried to change the value of the variable, that wouldn't be visible. For example, suppose this were valid:
int a = 7;
Runnable r = new Runnable() {
public void run() {
a = 5;
}
};
r.run();
System.out.println(a);
You might expect it to print 5 (which indeed it would in C#). But because only a copy has been taken, it would actually print 7... if it were allowed, with no bigger changes.
Of course, Java could have been changed to really capture the variable instead of its value (as C# was for anonymous functions). That requires automatically creating an extra class to store the "local" variables, and make both the method and the anonymous inner class share an instance of that extra class. That would have made anonymous inner classes more powerful, but arguably harder to understand. C# decided to go for the power-but-complexity route; Java went for the restrictive-but-simple approach.
(Using an array instead of a custom class is valid for a single variable, but becomes more wasteful when there are multiple variables involved - you don't really want to have to create a wrapper object for every variable if you can help it.)
Note that there are significant complexities involved in the capture-the-variable approach, at least using the C# rules. For example:
List runnables = new ArrayList();
int outer = 0;
for (int i = 0; i < 10; i++) {
int inner = 0;
runnables.add(new Runnable() {
public void run() {
outer++;
inner++;
}
});
}
How many "inner" variables are created? One for each instance of the loop, or one overall? Basically, scopes make life tricky for this sort of thing. Feasible, but tricky.