0

As you can see in the following code, I am accessing a JLabel from the ActionListener anonymous inner class. This provides me with no errors, so how is this allowed but if the JLabel was INSIDE the method is is not allowed without the final modifier?

JLabel e = new JLabel("");
        public void myMethod() {

            JButton b = new JButton("ok");
            b.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent arg0) {
                    e.setSize(200,200);

                }

            });

        }
mark
  • 2,841
  • 4
  • 25
  • 23
  • 1
    possible duplicate of [Java - Accessing variables from an anonymous inner class](http://stackoverflow.com/questions/10524635/java-accessing-variables-from-an-anonymous-inner-class): Both asks why is it OK to access non final field, which is not a local variable? Or did I miss something? If I did - please clarify. – amit May 10 '12 at 14:00

1 Answers1

4

If the variable is inside the method def, then it's a local variable -- and you are instantiating an object that will exist after that method's execution, and with it the lifetime of that local variable, has ended (it's in the method's stackframe that gets destroyed upon method return). For this to work at all some compiler magic is required, and this is usually called a closure, even if its current implementation in Java is so lame. The compiler will actually synthesize a class that implements ActionListener and has an instance variable to which the local variable's value will be copied.

It is a restriction specific to Java that you can only close over final vars due to thread-safety concerns. The story here goes that it is ingrained in the intuition of a Java developer that a local var is always thread-safe -- it can't change in the middle of method's execution (with no change done explicitly by that method), there can be no dataraces on it, etc. This would be violated if you could close over non-final vars as that closure could be executed in parallel with the currently executing method and change that var. That would lead to some very counterintuitive behavior.

There is, however, a (somewhat lame) workaround:

public void myMethod() {
    final JLabel[] e = {new JLabel("")};
    JButton b = new JButton("ok");
    b.addActionListener(new ActionListener() {
       @Override public void actionPerformed(ActionEvent arg0) {
         e[0].setSize(200,200);
    }});
}

e is now a one-element array, which resides on the heap and doesn't get destroyed with the method's stackframe. You can also apply your intuition to see that now it's definitely not thread-safe and that listener could easily change the value of that e[0] to something completely different, and the method shown here would observe that change with no explicit mutation code in it.

Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436
  • *"It is a restriction specific to Java that you can only close over final vars due to thread-safety concerns."* - Actually, the real reason is that non-final local variable access would require the JVM to support closures. The local variables for the outer method would need to remain live after the other method call ended!! You don't need multiple threads to trigger this ... – Stephen C Dec 30 '12 at 20:46
  • But... they **do** remain live. In the example, we dereference `e` each time an action is performed---way past `myMethod` having returned. – Marko Topolnik Dec 30 '12 at 22:20