Timers and Animation
An ActionListener is a functional interface whose single method, actionPerformed, receives an ActionEvent, so a lambda expression can supply it.
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?
Reveal 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.
- 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.
What an ActionListener Receives
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.
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.
- A declared object implements
ActionListener actionPerformedreceives theActionEventas evt- The Timer receives the object through the same two-argument constructor
| Aspect | Traditional form | Lambda form |
|---|---|---|
| Listener object | Declared with new ActionListener() and a body | Written inline as e -> repaint() |
| Method signature | public void actionPerformed(ActionEvent evt) | Matched automatically by the compiler |
| Parameter name | evt, chosen in the declaration | e, a name chosen by the writer and optional to use |
| Effect when the timer fires | Runs repaint() | Runs repaint() |
| Length | Longer, with more structure | Shorter |
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.
Timer timer = new Timer(4000, e -> repaint());The timer is constructed but never started, so the listener never runs.
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 likeTimer timer = new Timer(4000, e -> repaint()); with no timer.start() afterwards
Why it failsA timer that was never started never counts down, so its listener is never notified.
Do this insteadCall timer.start() after the timer is configured.
Common mistake Assuming a lambda body is correct because it compiles
What it looks likeTimer timer = new Timer(4000, e -> repaint()); when the panel should be calling a different method
Why it failsA lambda with a wrong body runs without complaint, so the error only shows up as wrong behaviour.
Do this insteadCheck 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 likee -> repaint() ignores e entirelyWhy it failsThe body does not have to use the parameter. The signature only has to match the method.
Do this insteadName the parameter and use it only when the body needs the event.
Rewriting a Listener
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
ActionListenerand overridesactionPerformedwith anActionEventparameter. - 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
ActionListeneris a functional interface with one method,actionPerformed, which receives anActionEvent, so a lambda can supply it. - A Timer constructor call takes a delay in milliseconds as its first argument and an
ActionListeneras 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
ActionListenerbehave identically; the lambda is simply shorter.