Why require local variables to be final when accessing from anonymous inner classes?

假装没事ソ 提交于 2019-12-05 20:43:41

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<Runnable> runnables = new ArrayList<Runnable>();
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.

Another problem you have (and can have in groovy which doesn't have this restriction) is that non final variables can change (otherwise you wouldn't have a problem making it final)

int a = 1; 
// use a in another thread, or potentially run it later
a = 2; 

Should the thread always see a = 1 or can it sometimes see a = 2.

The only way to write predictable anonymous classes is if the local variables don't change. The compiler could be smarter and detect when a variable can no longer change and that would simplify some odd cases IMHO. But the simplest solution is to require the local variable to be final.

BTW: An alternative which my IDE has an auto-fix for is to use an array of one instead.

final int[] a = { 1 };
//  use a[0] in an anonymous class.
a[0] = 2;

While ugly, it does make it unlikely you will accidentally write an anonymous class where the value could change.

Given that anonymous inner classes can outlive the scope in which they were created, I don't see how you're 'hack' would be easy to implement. The inner class still needs to maintain copies of all the variables it has closed around, and as such they cannot refer to variables that have been created on the stack.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!