1

I am learning the 'Core Java' book about how the local inner class access variables from outer methods. While, I am confused about something.

The book provides a snippet of code here:

public void start(int interval, boolean beep)
{
    class TimePrinter implements ActionListener
    {
        public void actionPerformed(ActionEvent event)
        {
            System.out.println("At the tone, the time is " + new Date());
            if (beep) Toolkit.getDefaultToolkit().beep();
        }
    }

    ActionListener listener = new TimePrinter();
    Timer t = new Timer(interval, listener);
    t.start();
}

The book illustrate how the flow of control here as:

  1. The start method is called.
  2. The listener is initialized.
  3. The listener reference is passed to the Timer constructor. At this point, the beep parameter variable of the start method no longer exists.
  4. Moment later, the actionPerformed method executes if (beep) ...

I am just confused about why the beep no longer exists? I thought the outer start method is still not coming to the end }, the local variable could still live...

Sjoerd222888
  • 3,228
  • 3
  • 31
  • 64
Oliver
  • 147
  • 9
  • 2
    so, you are declaring a class within a method? I very much doubt this will compile to begin with. – Stultuske Sep 01 '17 at 09:00
  • 1
    @Stultuske eclipse does not throw any error on that topic – XtremeBaumer Sep 01 '17 at 09:02
  • 1
    it is called local class, see [this example](https://docs.oracle.com/javase/tutorial/java/javaOO/localclasses.html). The compilation is successful. – DevDio Sep 01 '17 at 09:04
  • The point is that `TimePrinter.actionPerformed()` gets *executed* long time after the method has finished, mist likely from another thread... – Timothy Truckle Sep 01 '17 at 09:04
  • 1
    point 3 is wrong. you can still print the value at the end of the method – XtremeBaumer Sep 01 '17 at 09:05
  • @Stultuske Also, you can look at [this](https://stackoverflow.com/questions/2428186/use-of-class-definitions-inside-a-method-in-java) question for further information. – Shirkam Sep 01 '17 at 09:05
  • @Stultuske It's somewhat unorthodox, but that's valid Java. If you were to read the question fully, you would see that this is not even his code and that he's specifically asking about local classes. – Michael Sep 01 '17 at 09:06
  • @Michael yes, I did read that. I've also read posts of people that copied from a book, and ended up with four public classes in a single file, because they didn't realize they needed seperate files. Even when duplicating from a book, errors can come up – Stultuske Sep 01 '17 at 09:10
  • @Michael *sigh* dude, indeed, I was mistaken, which was pointed out several posts before yours. I merely responded to your "he copied it from a book, so it has to be right" point of view. – Stultuske Sep 01 '17 at 09:17

2 Answers2

2

The compiler knows that beep is effectively final and can therefore be assumed to not change. It will then arrange to build the local object as something like:

    class TimePrinter implements ActionListener {
        // Capture the local variable in this instance.
        boolean instanceBeep = beep;
        public void actionPerformed(ActionEvent event) {
            System.out.println("At the tone, the time is " + new Date());
            if (instanceBeep) Toolkit.getDefaultToolkit().beep();
        }
    }

However, this will only work if the beep parameter is effectively final, i.e. it does not change and could even be declared final beep without breaking the code.

To confirm, just try something like:

public void start(int interval, boolean beep) {
    class TimePrinter implements ActionListener {
        public void actionPerformed(ActionEvent event) {
            System.out.println("At the tone, the time is " + new Date());
            // Error:(17, 21) java: local variables referenced from an inner class must be final or effectively final
            if (beep) Toolkit.getDefaultToolkit().beep();
        }
    }
    // Add this to see the compiler working it out.
    beep = false;
    ActionListener listener = new TimePrinter();
    Timer t = new Timer(interval, listener);
    t.start();
}

and see that the accessing of the beep parameter is now not allowed because it is no longer effectively final.

See official Oracle doc on effectively final or the spec.

OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
1

If that is an exact quote from the book, the book is wrong. You are correct that the beep parameter of the start function does not go out of scope until the closing brace }. There is no special behaviour caused by the use of a local class.


In this scenario, a variable is said to have been "captured".

A variable needs to be either final or "effectively final" to be captured - i.e. it must never be reassigned.

In your case, you do not modify the value of beep so it is effectively final and eligible to be captured.

When you create the TimePrinter object, the value of beep is copied into its internal state. This is because the variable beep may have gone out of scope by the time it needs to be used.

The timer is started and the start method ends. The original beep has now gone out of scope. Every few seconds (depending upon interval) actionPerformed will now be called. The original beep has gone of scope, but we made a copy of its value so our function is still able to execute as we would expect.

That's pretty much the idea the author is trying to express.

Michael
  • 41,989
  • 11
  • 82
  • 128