Вопрос-ответ

Why are only final variables accessible in anonymous class?

Why are only final variables accessible in anonymous class?

  1. a can only be final here. Why? How can I reassign a in onClick() method without keeping it as private member?


    private void f(Button b, final int a){
    b.addClickHandler(new ClickHandler() {

    @Override
    public void onClick(ClickEvent event) {
    int b = a*5;

    }
    });
    }

  2. How can I return the 5 * a when it clicked? I mean,


    private void f(Button b, final int a){
    b.addClickHandler(new ClickHandler() {

    @Override
    public void onClick(ClickEvent event) {
    int b = a*5;
    return b; // but return type is void
    }
    });
    }

Переведено автоматически
Ответ 1

As noted in comments, some of this becomes irrelevant in Java 8, where final can be implicit. Only an effectively final variable can be used in an anonymous inner class or lambda expression though.


It's basically due to the way Java manages closures.

When you create an instance of an anonymous inner class, any variables which are used within that class have their values copied in via the autogenerated constructor. This avoids the compiler having to autogenerate various extra types to hold the logical state of the "local variables", as for example the C# compiler does... (When C# captures a variable in an anonymous function, it really captures the variable - the closure can update the variable in a way which is seen by the main body of the method, and vice versa.)

As the value has been copied into the instance of the anonymous inner class, it would look odd if the variable could be modified by the rest of the method - you could have code which appeared to be working with an out-of-date variable (because that's effectively what would be happening... you'd be working with a copy taken at a different time). Likewise if you could make changes within the anonymous inner class, developers might expect those changes to be visible within the body of the enclosing method.

Making the variable final removes all these possibilities - as the value can't be changed at all, you don't need to worry about whether such changes will be visible. The only ways to allow the method and the anonymous inner class see each other's changes is to use a mutable type of some description. This could be the enclosing class itself, an array, a mutable wrapper type... anything like that. Basically it's a bit like communicating between one method and another: changes made to the parameters of one method aren't seen by its caller, but changes made to the objects referred to by the parameters are seen.

If you're interested in a more detailed comparison between Java and C# closures, I have an article which goes into it further. I wanted to focus on the Java side in this answer :)

Ответ 2

There is a trick that allows anonymous class to update data in the outer scope.

private void f(Button b, final int a) {
final int[] res = new int[1];
b.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
res[0] = a * 5;
}
});

// But at this point handler is most likely not executed yet!
// How should we now res[0] is ready?
}

However, this trick is not very good due to the synchronization issues. If handler is invoked later, you need to 1) synchronize access to res if handler was invoked from the different thread 2) need to have some sort of flag or indication that res was updated

This trick works OK, though, if anonymous class is invoked in the same thread immediately. Like:

// ...

final int[] res = new int[1];
Runnable r = new Runnable() { public void run() { res[0] = 123; } };
r.run();
System.out.println(res[0]);

// ...
Ответ 3

An anonymous class is an inner class and the strict rule applies to inner classes (JLS 8.1.3):


Any local variable, formal method parameter or exception handler parameter used but not declared in an inner class must be declared final. Any local variable, used but not declared in an inner class must be definitely assigned before the body of the inner class.


I haven't found a reason or an explanation on the jls or jvms yet, but we do know, that the compiler creates a separate class file for each inner class and it has to make sure, that the methods declared on this class file (on byte code level) at least have access to the values of local variables.

(Jon has the complete answer - I keep this one undeleted because one might interested in the JLS rule)

Ответ 4

You can create a class level variable to get returned value. I mean

class A {
int k = 0;
private void f(Button b, int a){
b.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
k = a * 5;
}
});
}

now you can get value of K and use it where you want.

Answer of your why is :

A local inner class instance is tied to Main class and can access the final local variables of its containing method. When the instance uses a final local of its containing method, the variable retains the value it held at the time of the instance's creation, even if the variable has gone out of scope (this is effectively Java's crude, limited version of closures).

Because a local inner class is neither the member of a class or package, it is not declared with an access level. (Be clear, however, that its own members have access levels like in a normal class.)

java