Java local variable visibility in anonymous inner

2020-01-28 05:31发布

问题:

I don't understand why I cannot always access a variable from inside a 'listener' or 'handler'.

This is my code:

Button btnDownload = new Button(myparent, SWT.NONE);
btnDownload.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                btnDownload.setEnabled(false); // I CAN'T 
            }
        });

The only way is to declare it using the final keyword:

final Button btnDownload = new Button(myparent, SWT.NONE);

Why do I need to declare a variable final to gain access inside an event?

回答1:

Your SelectionAdapter is an anonymous inner class and I think this makes it clear:

Local classes can most definitely reference instance variables. The reason they cannot reference non final local variables is because the local class instance can remain in memory after the method returns. When the method returns the local variables go out of scope, so a copy of them is needed. If the variables weren’t final then the copy of the variable in the method could change, while the copy in the local class didn’t, so they’d be out of synch.

Anonymous inner classes require final variables because of the way they are implemented in Java. An anonymous inner class (AIC) uses local variables by creating a private instance field which holds a copy of the value of the local variable. The inner class isn’t actually using the local variable, but a copy. It should be fairly obvious at this point that a “Bad Thing”™ can happen if either the original value or the copied value changes; there will be some unexpected data synchronization problems. In order to prevent this kind of problem, Java requires you to mark local variables that will be used by the AIC as final (i.e., unchangeable). This guarantees that the inner class’ copies of local variables will always match the actual values.



回答2:

I believe that Tom is saying is that if you could use local variables in an anonymous class then which button should it enable in the following code segment.

    Button btnDownload = new Button(myparent, SWT.NONE);
    btnDownload.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    btnDownload.setEnabled(false); // I CAN'T 
                }
            });
   btnDownload = new Button(somethingelse , SWT.NONE);
   btnDownload = null ;

The makers of java wanted to avoid that debate by requiring local variables used in anonymous classes to be final.



回答3:

It's probably not the optimum design choice. In Java 8, along with lambda expression, this requirement of final hopefully will be dropped.

The goal is to forbid assigning to the local variable from the anonymous class. But this doesn't require marking the local variable as final.

void f()

  Object var = ...;
  new Anon(){
      ...
      print(var); // reading is ok
      var = x;    // error, can't write to var            [1]
  }

The compiler actually makes a copy of the var and save it in the anonymous class. The anonymous class only accesses the copy afterwards. The code above is actually transformed to

void f()

  Object var = ...;
  new Anon$1(var);

class Anon$1
{
    final Object $var;
    Anon$1(Object var){ this.$var=var; }
    ...
    print($var); 
    $var = x;    // error, naturally                      [2]
}

As you can see, there is no technical reason to require that var is final. All compiler has to do, when it encounters [2], knowing that $var is a synthesized field for var, report error "local variable var cannot be assigned to by the anonymous class" (instead of "$var is final and cannot be assigned to")

The language designers chose to annoy us by requiring the final keyword on the local variable; I don't remember the rationale; in general Java wasn't afraid of verbosity if clarity is desired.



回答4:

This post explains why

Java Method Variables & Anonymous Class



标签: java scope