Concepts / Timers and Animation

Timers and Animation

An ActionListener is a functional interface whose single method, actionPerformed, receives an ActionEvent, so a lambda expression can supply it.

  • Programming

A Timer That Only Counts

A timer on its own only counts time. It can be told to wait a number of milliseconds, but counting by itself does nothing visible. For a timer to be useful, something must happen when the counted time has passed. In Java Swing, that something is supplied by an object that responds to the timer's events. This is the idea behind the rest of this article: a timer is only half of a working animation, and the other half is the listener that reacts when the timer fires.

What do you think happens?

A Timer is created with a delay of 4000 milliseconds and a listener that calls repaint(), but timer.start() is never called. Does the listener body ever run?

  • Yes, after 4 seconds

  • No, the listener never runs

Reveal answer
Answer:

No, the listener never runs

The source is explicit that a Timer constructed but never started will never reach its listener. The timer must be started before its delay can count down, so the listener's code can be correct and still never execute.

Following the RandomArt Constructor

The starting point is a working program that repaints a panel after a delay. The constructor below belongs to a panel class named RandomArt. Read it once through and focus on the three things it does: it creates a timer, configures it with a delay and a listener, and then starts it. The sketch below is a reconstruction of that constructor showing only the parts the explanation needs.

java
Notes on the code
  • create: new Timer(...) builds the timer object
  • configure: 4000 is the delay in milliseconds and e -> repaint() is the listener
  • start: timer.start() begins the countdown

Read as a chain, the constructor creates the timer, hands it a listener, and starts it. The listener is the part that does the visible work. When the delay elapses, the listener's body calls repaint() and the panel redraws. The next section traces each hand-off in order, because the order is what determines where a broken timer stops.

RandomArt constructorcreates and starts thetimerTimercounts the delayActionListener lambdae -> repaint()RandomArt panelredraws itselfnew Timer(4000, listener)timer.start()delay elapses, listener notifiedrepaint()
What happens after timer.start() is called, which objects are involved in what order, and at which step would the listener body never run?

What an ActionListener Receives

Definition

An ActionListener is a functional interface whose single method, actionPerformed, receives an ActionEvent. Because it is a functional interface, a lambda expression can supply it.

The method signature is the contract. Any code handed to the timer as a listener must fit that signature. In the traditional declaration, the parameter is named evt. The parameter carries the event object, which is an ActionEvent. The listener body does not have to use that event at all: in the RandomArt example the body simply calls repaint() and ignores the event entirely.

The Timer constructor takes two arguments. The first is the delay, expressed in milliseconds. The second is the ActionListener that will be notified when the delay has elapsed. The diagram below labels each part of a Timer call so the roles are easy to see.

Timerthe timer being created4000delay in millisecondse ->lambda parameter for theeventrepaint()listener body run when thedelay elapses
In Timer(4000, e -> repaint()), which part is the delay, which part is the listener, and what does each argument do when the timer fires?

Lambda Versus Traditional Form

A lambda expression is a compact way of writing an object that implements a functional interface. Because ActionListener has only one method, the compiler can match the lambda to that method without extra naming. The traditional form below writes the same listener out as a named object with an explicit actionPerformed method.

java
Notes on the code
  • A declared object implements ActionListener
  • actionPerformed receives the ActionEvent as evt
  • The Timer receives the object through the same two-argument constructor
AspectTraditional formLambda form
Listener objectDeclared with new ActionListener() and a bodyWritten inline as e -> repaint()
Method signaturepublic void actionPerformed(ActionEvent evt)Matched automatically by the compiler
Parameter nameevt, chosen in the declaratione, a name chosen by the writer and optional to use
Effect when the timer firesRuns repaint()Runs repaint()
LengthLonger, with more structureShorter

Both forms reach the same listener body through the same Timer constructor.

Finding Where a Broken Timer Stops

When a timer-driven panel does not update, the useful question is where the chain of hand-offs stopped. Work through the sequence in order and check each link. The first question is whether start() was called at all. The second is whether the listener body is the one you intended, since a lambda with a wrong body will run without complaint. The third is whether the repaint call reaches the panel you are looking at.

Before
Timer timer = new Timer(4000, e -> repaint());

The timer is constructed but never started, so the listener never runs.

After
Timer timer = new Timer(4000, e -> repaint());
timer.start();

The timer is started, so the countdown begins and the listener runs when the delay elapses.

  • Common mistake

    Constructing the timer without calling start()

    What it looks like

    Timer timer = new Timer(4000, e -> repaint()); with no timer.start() afterwards

    Why it fails

    A timer that was never started never counts down, so its listener is never notified.

    Do this instead

    Call timer.start() after the timer is configured.

  • Common mistake

    Assuming a lambda body is correct because it compiles

    What it looks like

    Timer timer = new Timer(4000, e -> repaint()); when the panel should be calling a different method

    Why it fails

    A lambda with a wrong body runs without complaint, so the error only shows up as wrong behaviour.

    Do this instead

    Check that the body does what you intended, step by step, against the sequence of hand-offs.

  • Common mistake

    Expecting the event parameter to be used

    What it looks like
    e -> repaint() ignores e entirely
    Why it fails

    The body does not have to use the parameter. The signature only has to match the method.

    Do this instead

    Name the parameter and use it only when the body needs the event.

Rewriting a Listener

MEDIUM

Rewrite the following lambda-based Timer in its traditional anonymous-class form, keeping the 2000 millisecond delay and the body that calls repaint(). Then explain in one or two sentences why the two versions behave the same when the timer fires.

Timer timer = new Timer(2000, e -> repaint());
timer.start();
Hints
  • The traditional form declares a class that implements ActionListener and overrides actionPerformed with an ActionEvent parameter.
  • The Timer constructor still takes the delay first and the listener second.
  • Both versions have the same listener body and the same start() call, so the timing of the callback is identical.

Key Takeaways

  • A Timer only counts time; a listener supplies the behaviour that runs when the counted time has passed.
  • An ActionListener is a functional interface with one method, actionPerformed, which receives an ActionEvent, so a lambda can supply it.
  • A Timer constructor call takes a delay in milliseconds as its first argument and an ActionListener as its second.
  • The chain runs from constructor to timer.start() to the listener body; a timer that is never started never reaches its listener.
  • Lambda and traditional forms of the same ActionListener behave identically; the lambda is simply shorter.