Question

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);

                }

            });

        }
Was it helpful?

Solution

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top