Event Listeners and Timers

A Sprite that reads an image from a file and implements two interfaces at once, keyboard events delivered by methods the window system calls, and a Timer that replaces the while(true) loop that has driven every animation since Chapter 15. The last lesson of the book. Follows Think Java 2e, Chapter 17 (Advanced Topics), Sections 17.8-17.9, pp. 290-295, cross-referenced against The Java Tutorials — Introduction to Event Listeners.

Subject: Java · 65 slides · code lesson

Open the interactive version of this deck

What this lesson covers

The lesson, slide by slide

1. Event Listeners and Timers

Title

Think Java 2e · Chapter 17 · Advanced Topics

Sections 17.8-17.9 · pp. 290-295

2. What you will be able to do

Objectives

This lesson follows Think Java 2e, Chapter 17 (Advanced Topics), Sections 17.8-17.9, pp. 290-295. Everything on these slides can be checked against those pages.

1. Write a class that implements more than one interface.

2. Read an image from a file and handle the exception that may result.

3. Implement the three KeyListener methods and explain when each is invoked.

4. Use a switch statement with deliberate fall-through to handle two cases identically.

5. Register a listener and give a component the keyboard focus.

6. Replace an animation loop with a Timer and an ActionListener.

3. Retrieve before you read

Warm-up

Four things, and every one of them is about to be used at once.

Discussion prompt

From Lesson 17b: how many interfaces may a class implement, and what must it then provide? From Lesson 5a: what happens in a switch case with no break? And from Lesson 15b: what does try-catch do?

Hint: As many as needed. And execution falls through.

Answer:

Classes may implement as many interfaces as needed, and must provide every method each one declares. A case with no break falls through into the next one. And try-catch runs the try block, diverting to the catch only for the named exception type.

All three appear in Sprite, which implements two interfaces, catches an IOException, and uses fall-through deliberately. Now that our Drawing is based on Actor instead of DrawablePolygon, we can draw other types of graphics.

4. The framework calls you

Concept

Every program so far has driven itself: a loop you wrote, calling methods you wrote. Event-driven programming inverts that — you write methods and something else decides when to call them.

Figure (svg): Two panels contrasting a program that drives its own loop with one whose methods are called by the framework

Downey & Mayfield, Think Java, 2nd edition (Green Tea Press / O'Reilly, 2020) — Think Java 2e, Chapter 17 (Advanced Topics), Sections 17.8-17.9, pp. 290-295 — Section 17.8 opens on printed page 290.

5. The Sprite class

Section

Section 17.8

6. Two interfaces at once

Concept

Here is the beginning of a class that reads an image from a file and shows the image moving across the canvas. The class is called Sprite because a moving image is sometimes called a sprite, in the context of computer graphics.

public class Sprite implements Actor, KeyListener {
    private int xpos;
    private int ypos;
    private int dx;
    private int dy;
    private Image image;

    public Sprite(String path, int xpos, int ypos) {
        this.xpos = xpos;
        this.ypos = ypos;
        try {
            this.image = ImageIO.read(new File(path));
        } catch (IOException exc) {
            exc.printStackTrace();
        }
    }
}
fieldholds
xpos, yposthe location of the sprite
dx, dythe velocity in the x and y directions
imagethe picture, read from a file

sprite — A computer graphic that may be moved or otherwise manipulated on the screen.

Sprite implements two interfaces: Actor and KeyListener. It extends nothing at all — which is only possible because both of the things it needs to be are interfaces rather than classes.

7. Reading a file that might not be there

Notation

The constructor's three lines of file handling repay a close reading.

Annotate

  • IOException is one the compiler insists on — like InterruptedException in Lesson 15b, you cannot ignore it and still compile.
  • new File(path) does not open anything. A File object is a path; the reading happens in ImageIO.read.
  • printStackTrace is not an empty catch. It reports where the failure happened, which is what you want for a problem you cannot fix at run time.
  • But the constructor still returns, leaving image as null — so drawImage will later be handed a null. Reporting is not recovering.
  • Compare Lesson 15b's empty catch: an interrupted sleep genuinely does not matter, and a missing image file does. The catch block should differ accordingly.

A catch block should do something proportionate to what the exception means. Here the book chooses report and continue, which is honest for a teaching example and would deserve a harder look in real code.

8. The two Actor methods

Worked example

Actor requires that we provide draw and step methods.

public void draw(Graphics g) {
    g.drawImage(image, xpos, ypos, null);
}

public void step() {
    xpos += dx;
    ypos += dy;
}
methoddoescompare with
draw(g)draws the image at the current positionDrawablePolygon's fillPolygon
step()adds the velocity to the positionBlinkingPolygon's counter

draw places the image.

Why: The draw method draws the image at the sprite's current position.

The fourth argument is null.

Why: g.drawImage(image, xpos, ypos, null) — an optional observer this program does not need.

step moves it.

Why: The step method changes the position based on dx and dy, which are initially zero.

So it starts still.

Why: Until a key press sets dx or dy to something non-zero.

Verify: Notice that Drawing can hold this object even though it has nothing to do with polygons.

Why: Sprite and RegularPolygon share only Object and the Actor interface — one wraps an image file, the other computes vertices with trigonometry. That they can sit in the same list is exactly what Lesson 17b's generalisation was for.

9. What does Sprite extend?

Prediction

Look at the declaration.

public class Sprite implements Actor, KeyListener {
clausepresent?
extends?
implementstwo interfaces

Predict first

What is Sprite's superclass?

  • java.lang.Object — there is no extends clause
  • Actor
  • KeyListener
  • Drawing

Correct: java.lang.Object — there is no extends clause

Why: Classes that do not specify a superclass with extends automatically inherit from java.lang.Object — Lesson 14a's rule. Sprite implements two interfaces and extends nothing, which is exactly what interfaces make possible: two obligations, no inherited implementation, and its one extends still unspent.

10. Position and velocity

Concept

Four integers, and the split between them is the standard way to animate anything.

positionvelocity
fieldsxpos, yposdx, dy
changed bystepkeyPressed and keyReleased
meaningwhere it is nowhow far it moves per step
initiallythe constructor arguments0 — not moving

step reads velocity and writes position; the key handlers write velocity and never touch position. That separation is why holding a key produces smooth movement rather than a single jump — the key sets a rate, and the timer applies it.

11. Moving the sprite in keyPressed

Trap

The trap

Changing position directly on a key press.

public void keyPressed(KeyEvent e) {
    switch (e.getKeyCode()) {
        case KeyEvent.VK_UP:
            ypos -= 5;      // moves once per event
            break;
        ...
    }
}
what drives the motionconsequence
the key-repeat ratemovement stutters and depends on OS settings
a brief tapmoves exactly 5 pixels
holding the keya pause, then repeats

keyPressed is invoked repeatedly while a key is being held down, but at the operating system's key-repeat rate — with a delay before the first repeat. Motion driven by that is visibly uneven.

The fix

Set a velocity; let step apply it.

public void keyPressed(KeyEvent e) {
    case KeyEvent.VK_UP:
        dy = -5;            // sets a rate
}

public void step() {
    xpos += dx;             // applied every frame
    ypos += dy;
}
position in keyPressedvelocity plus step
driven bykey repeatthe timer, 30 times a second
holding a keystutteringconstant speed
releasing itjust stops repeatingkeyReleased sets it to 0

The values of dx and dy determine how much the sprite moves each time step is invoked. While the user holds down an arrow key, the sprite will move at a constant speed. Input sets intent; the clock sets timing.

12. Position or velocity?

Definition probe

Which methods write which fields.

Sort into buckets

Sort each field or action.

position
xpos; what step changes
velocity
dx; what keyPressed changes
pos
Where the sprite currently is on the canvas — read by draw and updated once per time step.
vel
How far it moves per step. Set by the key handlers and applied by step, which is what makes motion smooth rather than stuttering.

13. Read the image

Fill the middle

The file might not exist.

Fill in the blanks

try File}(path));
} catch (IOException exc) ___

Why: new File(path) builds a path object without opening anything; ImageIO.read does the reading and may fail. IOException is a checked exception, so Java refuses to compile the call unless it is caught or declared — the same rule that forced the try-catch around Thread.sleep in Lesson 15b.

14. Is printStackTrace enough?

Socratic

The constructor catches, reports, and returns.

Discussion prompt

If the image file is missing, image stays null and the constructor returns normally. What happens later, and what would be better?

Hint: What does drawImage do with null?

Answer:

The object is left in a broken state. Every later draw call passes a null image, and the sprite simply never appears — with the only clue being a stack trace printed at startup.

Better options: rethrow as an unchecked exception so construction fails loudly, or substitute a placeholder image so the program stays usable.

Catching is not the same as handling. The question to ask is always: after this catch block, is the program still correct? Here it is not, which makes the choice defensible for a book example and wrong for real code.

15. The KeyListener interface

Section

Section 17.8

16. Three methods the system calls

Concept

KeyListener is an interface for receiving keyboard events, which means we can detect and respond to key presses. A class that implements KeyListener has to provide the following methods.

void keyPressed(KeyEvent e)
void keyReleased(KeyEvent e)
void keyTyped(KeyEvent e)
methodinvoked when
keyPresseda key has been pressed — repeatedly while held down
keyReleaseda key has been released, meaning it is no longer down
keyTypeda key has been typed — generally pressed and released

These methods get invoked when the user presses and releases any key. They take a KeyEvent object as a parameter, which specifies which key was pressed, released, or typed. You never call them; the window system does — which is what event-driven means.

17. Three events for one keystroke

Notation

Pressing and releasing a key produces three separate calls, and knowing which is which matters.

Annotate

  • keyPressed repeats. Hold a key and the method is called over and over, at the operating system's repeat rate.
  • keyReleased fires exactly once, which is what makes it the right place to stop the motion.
  • keyTyped is about characters, not keys — which is why it is the wrong one for arrow keys, since they type nothing.
  • All three take a KeyEvent, which carries which key was involved.
  • You must provide all three, whether or not you want them — an interface is all-or-nothing, as Lesson 17b established.

Press-and-hold is a state, not an event. The design here uses keyPressed to enter that state and keyReleased to leave it — which is why the sprite moves for exactly as long as the key is down.

18. Responding to the arrow keys

Worked example

We can use these methods to design a simple animation using the arrow keys.

public void keyPressed(KeyEvent e) {
    switch (e.getKeyCode()) {
        case KeyEvent.VK_UP:
            dy = -5;
            break;
        case KeyEvent.VK_DOWN:
            dy = +5;
            break;
        case KeyEvent.VK_LEFT:
            dx = -5;
            break;
        case KeyEvent.VK_RIGHT:
            dx = +5;
            break;
    }
}
keysetseffect
VK_UPdy = −5moves up the screen
VK_DOWNdy = +5moves down
VK_LEFTdx = −5moves left
VK_RIGHTdx = +5moves right
anything elsenothingignored

Ask the event which key it was.

Why: e.getKeyCode() returns an int, and KeyEvent.VK_UP and friends are the named constants.

Switch on it.

Why: Here's an implementation of keyPressed that uses a switch statement to test which arrow key was pressed and sets dx or dy accordingly.

Up is negative.

Why: Screen coordinates increase downward — the same convention as Lesson 15a's grid.

Ignore everything else.

Why: There is no default branch, so we ignore all other keys.

Verify: Notice VK_UP is a named constant, not a magic number — KeyEvent.VK_UP rather than 38.

Why: Lesson 3a's argument for named constants, in a library. The numeric key codes exist, and nobody should ever type one: the name says what it means and the compiler catches a misspelling.

19. Which method fires repeatedly?

Prediction

Hold an arrow key down.

void keyPressed(KeyEvent e)
void keyReleased(KeyEvent e)
void keyTyped(KeyEvent e)
actionwhich method
press and hold?
let gokeyReleased

Predict first

Which is invoked repeatedly while a key is held?

  • keyPressed
  • keyReleased
  • keyTyped
  • All three

Correct: keyPressed

Why: This method is invoked repeatedly while a key is being held down. That repetition at the operating system's key-repeat rate is exactly why the sprite sets a velocity here rather than moving — the timing of the repeats is uneven and out of the program's control.

20. Stopping the motion

Concept

When the user releases the key, keyReleased sets dx or dy to 0, so the sprite stops moving in that direction.

public void keyReleased(KeyEvent e) {
    switch (e.getKeyCode()) {
        case KeyEvent.VK_UP:
        case KeyEvent.VK_DOWN:
            dy = 0;
            break;
        case KeyEvent.VK_LEFT:
        case KeyEvent.VK_RIGHT:
            dx = 0;
            break;
    }
}
casefalls through tosets
VK_UPthe VK_DOWN casedy = 0
VK_DOWN—dy = 0
VK_LEFTthe VK_RIGHT casedx = 0
VK_RIGHT—dx = 0

The two vertical cases share one body, because releasing either up or down should stop vertical motion. This is Lesson 5a's fall-through used deliberately — two labels on one block, with the break only after the shared code.

21. Forgetting a required method

Trap

The trap

KeyListener declares three, and you must supply three.

public class Sprite implements Actor, KeyListener {
    public void keyPressed(KeyEvent e) { ... }
    public void keyReleased(KeyEvent e) { ... }
    // no keyTyped
}

// error: Sprite is not abstract and does not override
//        abstract method keyTyped(KeyEvent) in KeyListener
KeyListener declaresSprite provides
keyPressedyes
keyReleasedyes
keyTypedno — compile error

We don't need the keyTyped method for this example, but it's required by the interface; if we don't provide one, the compiler will complain. An interface is a complete contract.

The fix

Provide it, empty, with a comment saying so.

public void keyTyped(KeyEvent e) {
    // do nothing
}
why it is acceptable here
arrow keys type no characterkeyTyped never fires for them
the sprite ignores other keys anywaynothing is lost
the commentmarks it as a decision, not an oversight

So we provide an implementation that does nothing. The same judgement as Lesson 15b's empty catch: an empty body is fine when doing nothing is genuinely correct, and the comment is what tells the next reader that you decided rather than forgot.

22. What happens with no default branch?

Prediction

The switch lists only four cases.

switch (e.getKeyCode()) {
    case KeyEvent.VK_UP:    dy = -5; break;
    case KeyEvent.VK_DOWN:  dy = +5; break;
    case KeyEvent.VK_LEFT:  dx = -5; break;
    case KeyEvent.VK_RIGHT: dx = +5; break;
}
// the user presses 'q'
keymatches a case?
VK_UPyes
'q'no

Predict first

What happens when another key is pressed?

  • Nothing — no case matches and there is no default, so the method returns
  • A run-time error
  • The last case runs
  • The method is not called at all

Correct: Nothing — no case matches and there is no default, so the method returns

Why: There is no default branch, so we ignore all other keys. The method is still invoked — the system sends every key event to every registered listener — but the switch matches nothing and the body completes without changing anything.

23. Stop on release

Fill the middle

Two keys, one shared body.

Fill in the blanks

case KeyEvent.VK_LEFT:
case KeyEvent.VK_RIGHT:
dx = 0;
break;

Why: Left and right both affect horizontal motion, so releasing either sets dx to zero. The first label has no body at all, which makes execution fall through to the second — Lesson 5a's fall-through used deliberately, with the break after the shared code.

24. What if you hold two arrows at once?

Edge cases

Up and right together.

Discussion prompt

The user presses up and then right without releasing either. Work out what dx and dy become, and what happens when only one is released.

Hint: Two separate fields.

Answer:

Both are set: dy = −5 and dx = +5, so the sprite moves diagonally. The two key handlers touch different fields, so neither interferes with the other.

Releasing right calls keyReleased, which sets dx = 0 and leaves dy alone — so the sprite keeps moving up. That is the correct behaviour, and it comes free.

Storing the state in two independent fields is what makes diagonals work without any code for them. A single direction field would have needed eight cases and would still handle release badly.

25. Registering the listener

Section

Section 17.8

26. Implementing is not enough

Concept

Writing the three methods makes Sprite capable of receiving key events. It does not make anything send them.

Sprite sprite = new Sprite("face-smile.png", 25, 150);
drawing.add(sprite);
drawing.addKeyListener(sprite);
drawing.setFocusable(true);
linedoes
drawing.add(sprite)adds it to the list of Actors to draw and step
drawing.addKeyListener(sprite)adds it to the list that will receive key events
drawing.setFocusable(true)gives the canvas the keyboard focus

Recall that the add method is one that we wrote in Section 17.5. It adds an Actor to the list of objects to be drawn. The second line is a different list entirely — and the same object is on both, which is why it implements two interfaces.

27. Two lists, and the focus

Notation

Three lines and every one of them is necessary. Missing any produces a different silent failure.

Annotate

  • Two separate registrations. The same object appears on both lists, in two different roles.
  • addKeyListener is inherited from Canvas — Drawing did not write it, and Lesson 17b's extends Canvas is what supplies it.
  • Without add, the sprite responds to keys and is never drawn.
  • Without addKeyListener, it is drawn and never moves — the keys go nowhere.
  • The setFocusable method ensures that drawing will have the focus initially, without the user having to click it first.

Key events are sent to components only when they have the keyboard focus. That is a rule about windows, not about Java — and forgetting it produces a program that works only after you click on it.

28. Diagnosing the three failures

Worked example

Each missing line fails silently and differently. Being able to tell them apart is most of debugging event code.

// what is missing if...
//   the sprite never appears           -> drawing.add
//   the sprite appears but never moves -> addKeyListener
//   it moves only after you click it   -> setFocusable
symptomcausefix
nothing drawnnot in the Actor listdrawing.add(sprite)
drawn, ignores keysnot registered as a listenerdrawing.addKeyListener(sprite)
works after a clickno initial focusdrawing.setFocusable(true)
nothing at all happensno timer startedSection 17.9

Ask whether it is drawn.

Why: If not, the problem is the Actor list — nothing to do with events.

Ask whether keys do anything.

Why: If not, the listener registration is missing.

Ask whether clicking first helps.

Why: If it does, the component did not have focus.

Ask whether anything animates.

Why: If nothing moves at all, step is never being called.

Verify: Comment out setFocusable(true), run it, and click the canvas: the sprite starts responding.

Why: That symptom is unmistakable once you have seen it, and baffling before. Event-driven bugs rarely produce errors — they produce silence, so the diagnosis has to come from which part of the behaviour is missing.

29. What does setFocusable do?

Prediction

Key events go somewhere specific.

drawing.setFocusable(true);
detail
key events go tothe component with keyboard focus
without this call?

Predict first

What happens if you omit it?

  • The canvas may not receive key events until the user clicks it
  • The program does not compile
  • The sprite is never drawn
  • The keys work but the sprite moves twice as fast

Correct: The canvas may not receive key events until the user clicks it

Why: In graphical applications, key events are sent to components only when they have the keyboard focus. The setFocusable method ensures that drawing will have the focus initially, without the user having to click it first. It is a run-time wiring detail the compiler cannot check.

30. One object, two roles

Concept

Sprite is on two lists because it implements two interfaces, and each list wants a different capability.

listwantscalls
Drawing's Actor listActordraw and step
Canvas's listener listKeyListenerkeyPressed, keyReleased, keyTyped
what the object isboth—

Neither list knows about the other. Drawing's list holds Actors and would be perfectly happy with a polygon; the listener list holds KeyListeners and knows nothing about drawing. The object satisfies both contracts independently — which is the practical payoff of implements as many as needed.

31. Expecting events without registering

Trap

The trap

Implementing KeyListener does not subscribe you to anything.

Sprite sprite = new Sprite("face-smile.png", 25, 150);
drawing.add(sprite);
// no addKeyListener

// the sprite is drawn, and the arrow keys do nothing
done?
implements KeyListeneryes
provides the three methodsyes
registered with a componentno
receives eventsno

**Implementing an interface says I can do this; registering says send them to me.** The compiler checks the first and knows nothing about the second, so this failure is silent.

The fix

Implement, then register, then give it focus.

drawing.add(sprite);              // to be drawn
drawing.addKeyListener(sprite);   // to receive key events
drawing.setFocusable(true);       // so key events arrive at all
stepwithout it
implement the interfacewill not compile
register the listenercompiles, receives nothing
make the component focusablereceives nothing until clicked

The first row is the only one the compiler checks. The other two are run-time wiring — which is the general shape of event-driven code, and why it is worth checking the registrations first when nothing happens.

32. Which list, which method?

Definition probe

The same object on two lists.

Sort into buckets

Sort each method by which registration causes it to be called.

drawing.add
draw(Graphics g); step()
drawing.addKeyListener
keyPressed(KeyEvent e); keyReleased(KeyEvent e)
add
The Actor list, which Drawing walks when it paints and when it steps.
key
The listener list, inherited from Canvas, which the window system consults when a key event occurs.

33. Wire it up

Fill the middle

Draw it, listen to it, focus it.

Fill in the blanks

drawing.add(sprite);
drawing.addKeyListener(sprite);
drawing.setFocusable(true);

Why: addKeyListener is inherited from Canvas and subscribes the object to key events; setFocusable makes the canvas eligible to receive them without a click first. Both are run-time wiring, so omitting either compiles perfectly and simply does nothing.

34. Why are these two lists separate?

Explain it to yourself

The same object is on both.

Discussion prompt

Drawing could have registered every Actor it holds as a key listener automatically. Why is it better that the two are separate calls?

Hint: Does every drawable thing want keyboard input?

Answer:

Because most drawable things do not want key events. A blinking polygon has no use for them, and forcing every Actor to be a KeyListener would mean three empty methods on every shape.

And the reverse: a key listener need not be drawable. A class handling shortcuts might listen and never appear on screen.

Two capabilities, two interfaces, two registrations. Keeping them independent is what lets a class opt into exactly the ones it wants — and it is the same argument as keeping Actor down to two methods in Lesson 17b.

35. Timers

Section

Section 17.9

36. A better way to animate

Concept

Now that you know about interfaces and events, we can show you a better way to create animations. Previously, we implemented the animation loop by using while (true) and Thread.sleep. Java provides a Timer class (in javax.swing) that encapsulates this behavior.

// The constructor for Timer takes two parameters:
//   int delay                  // milliseconds between events
//   ActionListener listener    // for handling timer events

Timer timer = new Timer(33, game);
timer.start();
while (true) loopTimer
you writethe loop, the sleep, the catchone method
who drivesyour codethe Timer
exception handlingInterruptedException, alwaysnone
linesabout 82

A Timer is useful for executing code at regular intervals. It is the same idea as while (true) with a sleep — packaged, so that the loop, the delay and the exception handling are the library's problem rather than yours.

37. An interface with one method

Notation

The ActionListener interface requires only one method, actionPerformed.

Annotate

  • One method is all the interface requires, so implementing it costs three lines.
  • 33 milliseconds is about 30 frames per second — the same rate as Lesson 17b's 1000 / 30.
  • The Timer holds a reference to the listener and calls it; nothing in your code waits for anything.
  • main returns immediately after timer.start(), and the program keeps running because the window and the timer are still alive.
  • This is the same inversion as paint: you supply a method, the framework decides when.

Three interfaces in two lessons — Actor, KeyListener, ActionListener — and all three exist so that something else can call your code. That is what event-driven programming is.

38. The VideoGame class

Worked example

Using a Timer, we can reorganize the code in main by defining a class that implements ActionListener.

public class VideoGame implements ActionListener {
    private Drawing drawing;

    public VideoGame() {
        Sprite sprite = new Sprite("face-smile.png", 50, 50);
        drawing = new Drawing(800, 600);
        drawing.add(sprite);
        drawing.addKeyListener(sprite);
        drawing.setFocusable(true);

        JFrame frame = new JFrame("Video Game");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(drawing);
        frame.pack();
        frame.setVisible(true);
    }

    public void actionPerformed(ActionEvent e) {
        drawing.step();
    }

    public static void main(String[] args) {
        VideoGame game = new VideoGame();
        Timer timer = new Timer(33, game);
        timer.start();
    }
}
stepwhat happens
new VideoGame()creates a Sprite, a Drawing and a JFrame
new Timer(33, game)creates a timer that will call game
timer.start()the timer begins firing
every 33 msactionPerformed runs, calling drawing.step()
drawing.step()steps every Actor, then repaints

The constructor builds everything.

Why: The main method constructs a VideoGame object, which creates a Sprite, a Drawing, and a JFrame.

Then main creates the timer.

Why: Then it constructs a Timer object and starts the timer.

The timer calls back.

Why: Every 33 milliseconds, the Timer invokes actionPerformed, which invokes step on the Drawing.

Which cascades.

Why: Drawing.step invokes step on all of its Actor objects, which causes them to update their position, color, or other aspects of their appearance. The Drawing.step then repaints the Canvas, and the time step is done.

Verify: Notice there is no loop anywhere in this program.

Why: The while (true) that has driven every animation since Chapter 15 is gone, and nothing replaced it in your code — the loop now lives inside the Timer. That is the whole point of the section.

39. How many times per second does actionPerformed run?

Prediction

The delay is in milliseconds.

Timer timer = new Timer(33, game);
timer.start();
value
delay33 ms
1000 / 33?

Predict first

Roughly how many times per second?

  • About 30
  • About 33
  • About 3
  • Once

Correct: About 30

Why: A thousand milliseconds divided by 33 is about 30, which is the standard frame rate for smooth animation — and the same rate Lesson 17b's loop achieved with Thread.sleep(1000 / 30). The Timer takes the delay directly rather than a rate.

40. The chain of calls, once per frame

Concept

One timer tick reaches every object on the canvas, through four calls that were each written separately.

Figure (svg): A pipeline from the timer through actionPerformed and Drawing step to each actor and a repaint

Each link was written in a different section and none of them knows about the others. The Timer knows only ActionListener; VideoGame knows only Drawing; Drawing knows only Actor. Every join is an interface, which is why the pieces could be built independently.

41. Doing slow work in actionPerformed

Trap

The trap

The timer's thread is the one drawing your window.

public void actionPerformed(ActionEvent e) {
    drawing.step();
    loadNextLevelFromDisk();     // takes two seconds
}
consequence
the method takes 2 secondsno repainting for 2 seconds
the windowappears frozen
key eventsqueue up unhandled
the next timer tickdelayed

Everything the window system does is on one thread, including calling your paint, your key handlers, and your actionPerformed. A slow method there stops all of it — which is why an unresponsive window is the classic symptom.

The fix

Keep event handlers short.

public void actionPerformed(ActionEvent e) {
    drawing.step();     // update and repaint - fast
}
handlershould take
actionPerformedmuch less than the timer interval
keyPressedessentially no time — it sets two fields
paintas long as drawing takes, and no longer

actionPerformed here is one line, and keyPressed sets a single field. That is not minimalism for its own sake — an event handler runs on the thread that keeps the interface alive, so short is part of the contract.

42. What replaces the while loop?

Prediction

There is no loop in VideoGame at all.

public static void main(String[] args) {
    VideoGame game = new VideoGame();
    Timer timer = new Timer(33, game);
    timer.start();
}
beforeafter
the repetitionwhile (true)?

Predict first

Where does the repetition happen now?

  • Inside the Timer, which calls actionPerformed at intervals
  • Inside actionPerformed, which loops
  • Inside Drawing.step
  • There is no repetition; it runs once

Correct: Inside the Timer, which calls actionPerformed at intervals

Why: Java provides a Timer class that encapsulates this behavior. The loop still exists — it is just inside the library rather than in your code, which is why main can return immediately while the animation keeps running.

43. Who calls what?

Definition probe

Some methods you call; some are called on you.

Sort into buckets

Sort each method.

you call it
timer.start(); drawing.add(sprite)
the framework calls it
actionPerformed(ActionEvent e); keyPressed(KeyEvent e)
you
Ordinary method calls from your own code, setting things up or asking the library to do something.
them
Callbacks — you write the method and something else decides when it runs. That inversion is what makes the program event-driven.

44. Why is a Timer better than a loop?

Real world

The while loop worked for three chapters.

Discussion prompt

while (true) with Thread.sleep animated Conway, Langton and the polygons perfectly well. What does a Timer actually improve?

Hint: What else would you want the program to do at the same time?

Answer:

The loop occupies whatever thread runs it. With a Timer, main returns and the program is free to respond to key presses, window resizing and everything else while the animation continues.

And the timer runs on the same thread as the drawing, so an update and a repaint never collide — which a hand-written loop on another thread can do, with results that are very hard to debug.

Plus it is less code and no exception handling. The try/catch around Thread.sleep disappears entirely — a small thing, repeated in every animation you would ever write.

45. What the book built

Section

Sections 17.8-17.9 and back

46. Every interface exists so something can call you

Concept

At this point, you have all of the elements you need to write your own video games. Four callback mechanisms have appeared, and they are all the same shape.

you providedeclared bycalled bywhen
paint(Graphics)Canvasthe window systemthe window needs redrawing
draw and stepActorDrawingevery frame
keyPressed and friendsKeyListenerthe window systema key event occurs
actionPerformedActionListenerTimerevery 33 milliseconds

In every row you write a method and something else decides when it runs. That inversion is what event-driven means, and interfaces are what make it possible — the caller needs a type it can rely on without knowing your class.

47. The finished program

Picture it

Six classes and three interfaces, and no two of them are tightly coupled.

Figure (svg): A UML diagram of VideoGame, Timer, Drawing, Sprite and the three interfaces they implement

Sprite implements two interfaces and extends nothing; VideoGame implements one. Every arrow into an interface is a promise that lets some other class call in without knowing what it is calling.

48. Tracing one frame

Worked example

The user is holding the right arrow key. Follow 33 milliseconds all the way through.

// the user holds RIGHT; the timer fires
#whocallseffect
1the window systemsprite.keyPressed(e)dx = +5
2Timergame.actionPerformed(e)—
3VideoGamedrawing.step()—
4Drawingsprite.step()xpos += 5
5Drawingrepaint()asks for a redraw
6the window systemdrawing.paint(g)—
7Drawingsprite.draw(g)the image appears 5 pixels right

Two independent sources of calls.

Why: The key event and the timer tick arrive separately and do not know about each other.

The key handler only sets a field.

Why: It does not draw, move or repaint anything.

The timer drives the update.

Why: Which is why the sprite moves at a constant rate regardless of key repeat.

And repaint closes the loop.

Why: You ask; the system calls paint; paint asks each Actor to draw itself.

Verify: Count how many of those seven steps are calls you wrote versus calls made into your code: three in, four out.

Why: Three entry points — keyPressed, actionPerformed, paint — and none of them is called by your program. That ratio is what distinguishes an event-driven program from the console programs of Chapters 1 to 13.

49. Which interface declares it?

Definition probe

Three interfaces in this program.

Sort into buckets

Sort each method.

Actor
draw(Graphics g); step()
KeyListener
keyReleased(KeyEvent e)
ActionListener
actionPerformed(ActionEvent e)
actor
The interface Drawing requires — two methods, draw and step, and nothing else.
key
The keyboard interface, which requires three methods: keyPressed, keyReleased and keyTyped.
action
The single-method interface a Timer calls when it fires.

50. What the whole book covered

Concept

We hope this final chapter has been a helpful summary of topics presented throughout the book.

topicchapterswhere it reappeared here
input and output3, 9reading an image file
decisions and loops5, 6the switch in keyPressed
classes and methods4, 11Sprite and VideoGame
arrays and objects7, 10, 15the ArrayList of Actors
inheritance14, 16, 17DrawablePolygon, and three interfaces
graphics15, 16, 17all of it

Congratulations on making it to the end! Every technique in this last program was introduced somewhere earlier — the switch statement in Chapter 5, the try-catch in Chapter 15, the interfaces two sections ago. The chapter is a summary as much as a new topic.

51. Thinking event-driven means concurrent

Trap

The trap

Assuming your handlers run at the same time.

// "keyPressed and actionPerformed are both being called,
//  so I need to worry about them interfering"

public void keyPressed(KeyEvent e) {
    synchronized (this) { dx = -5; }    // unnecessary here
}
assumptionreality
handlers run concurrentlythey run one at a time
you need lockingnot for this program
what is actually trueone handler finishes before the next starts

Swing delivers events on a single thread, so keyPressed and actionPerformed never overlap. Adding synchronisation here protects against something that cannot happen.

The fix

One thread, one handler at a time — which is why they must be short.

public void keyPressed(KeyEvent e) {
    dx = -5;              // no locking needed
}

public void actionPerformed(ActionEvent e) {
    drawing.step();       // runs only when no handler is running
}
consequencedetail
no data races between handlersthey are serialised
a slow handler blocks everythingincluding repainting
so keep them shortthe real rule

The single thread is a simplification, not a limitation to work around — it is what makes the earlier hazard the important one. You do not need locks; you do need handlers that finish quickly.

52. What holds the program open after main returns?

Prediction

main creates a timer, starts it, and ends.

public static void main(String[] args) {
    VideoGame game = new VideoGame();
    Timer timer = new Timer(33, game);
    timer.start();
}
after main returnsstill alive?
the windowyes
the timeryes

Predict first

Why does the program keep running?

  • The window and the timer keep running on their own threads until the window is closed
  • main never actually returns
  • The Timer blocks main
  • It does not — the program exits immediately

Correct: The window and the timer keep running on their own threads until the window is closed

Why: timer.start() returns immediately, and so does main — but the graphical interface and the timer continue running. The program ends when the window is closed, which is what setDefaultCloseOperation(EXIT_ON_CLOSE) arranged back in Lesson 15a.

53. Match the callback to its caller

Matching

Four methods you write and never call.

Match the pairs

  • a. paint(Graphics g)
  • b. keyPressed(KeyEvent e)
  • c. actionPerformed(ActionEvent e)
  • d. step()
  • r1. the window system, when redrawing is needed
  • r2. the window system, when a key goes down
  • r3. the Timer, every 33 milliseconds
  • r4. Drawing, once per frame for each Actor

Why: All four are callbacks — methods you write for something else to invoke. The fourth is the interesting one: Drawing is your own class acting as a framework, calling into objects it knows only through the Actor interface.

54. What could you build from here?

Real world

At this point, you have all of the elements you need to write your own video games.

Discussion prompt

You now have shapes, images, keyboard input, a timer and a canvas. What is genuinely missing for a simple game, and which of it do you already know how to build?

Hint: What happens when two things touch?

Answer:

Collision detection is the obvious gap — and Polygon already provides contains and intersects, which Lesson 17a mentioned and never used.

Scoring, levels and state are just fields and conditionals; sound and menus would need new libraries, but the pattern would be the same — an interface, and something that calls it.

The structural work is done. Adding a new kind of object means writing a class that implements Actor, and Drawing needs no change at all — which is the return on the interface, and a fair place to end the book.

55. The loop against the Timer

Comparison

Fill the blanks.

Comparison matrix

while (true) loopTimer
who repeatsyour codethe Timer
the delayThread.sleepthe constructor's first argument
exception handlingtry-catch for InterruptedExceptionnone
what you providethe whole loopactionPerformed
does main return?no — it runs foreveryes, immediately

The last row is the practical difference. With a Timer, main finishes and the program is free to respond to everything else — which is why every graphical framework works this way.

56. The pattern to carry away

Pattern

Implement an interface, register the object, and let the framework call you.

// 1. implement the interface the framework declares
public class Sprite implements Actor, KeyListener {
    public void draw(Graphics g)      { ... }
    public void step()                { ... }
    public void keyPressed(KeyEvent e)  { dx = +5; }
    public void keyReleased(KeyEvent e) { dx = 0; }
    public void keyTyped(KeyEvent e)  { /* do nothing */ }
}

// 2. register the object with whatever sends the events
drawing.add(sprite);              // Actor
drawing.addKeyListener(sprite);   // KeyListener
drawing.setFocusable(true);       // so events arrive

// 3. let something else drive the repetition
Timer timer = new Timer(33, game);
timer.start();
stepchecked by the compiler?symptom if missing
implement every methodyeswill not compile
register the listenernosilently receives nothing
make the component focusablenoworks only after a click
start the timernonothing animates

57. Check: keyPressed

Check

Work it out before you click.

public void keyPressed(KeyEvent e) {
    switch (e.getKeyCode()) {
        case KeyEvent.VK_UP:
            dy = -5;
            break;
        ...
    }
}
detail
when the user holds the key?

Check your understanding

Why does keyPressed set dy rather than changing ypos directly?

  • A. So the timer moves the sprite at a constant rate, rather than at the operating system's key-repeat rate (correct)
  • B. Because ypos is private
  • C. Because keyPressed is called only once
  • D. Because dy is faster to compute

Answer: A

Why: keyPressed is invoked repeatedly while a key is being held down — but at the operating system's repeat rate, with a delay before the first repeat, which produces visibly uneven motion. Setting a velocity lets step apply it once per frame: while the user holds down an arrow key, the sprite will move at a constant speed.

Why B tempts people
It is private, but keyPressed is inside Sprite and could assign it freely.
Why C tempts people
The opposite — it is called repeatedly, which is precisely the problem.
Why D tempts people
Both are single integer assignments; speed is not the issue.

58. Check: registration

Check

Work it out before you click.

Sprite sprite = new Sprite("face-smile.png", 25, 150);
drawing.add(sprite);
drawing.setFocusable(true);
// drawing.addKeyListener(sprite);  <- omitted
listsprite on it?
Actor listyes
listener listno

Check your understanding

What happens?

  • A. The sprite is drawn but never responds to the arrow keys (correct)
  • B. A compile error, since Sprite implements KeyListener
  • C. The sprite is never drawn
  • D. It works exactly the same

Answer: A

Why: Implementing KeyListener says the class can receive key events; addKeyListener is what makes something send them. The compiler checks the first and knows nothing about the second, so the omission compiles perfectly and produces a sprite that is drawn, stepped, and completely unresponsive.

Why B tempts people
Implementing an interface and registering with a component are unrelated; nothing requires the latter.
Why C tempts people
drawing.add is present, so it is on the Actor list and gets drawn.
Why D tempts people
Without the registration, no key event ever reaches the sprite.

59. Check: Timer

Check

Work it out before you click.

VideoGame game = new VideoGame();
Timer timer = new Timer(33, game);
timer.start();
argumentmeaning
33?
game?

Check your understanding

What do the two arguments mean?

  • A. A delay of 33 milliseconds, and the ActionListener whose actionPerformed will be called (correct)
  • B. 33 frames per second, and the window to repaint
  • C. 33 repetitions, and the object to animate
  • D. A delay of 33 seconds, and a callback

Answer: A

Why: The constructor for Timer takes two parameters: int delay — milliseconds between events — and ActionListener listener, for handling timer events. 33 milliseconds works out at about 30 frames per second, and game qualifies because VideoGame implements ActionListener. The timer repeats until stopped, so 33 is not a count.

Why B tempts people
The parameter is a delay in milliseconds, not a rate — 30 frames per second is the consequence, not the argument.
Why C tempts people
The timer repeats indefinitely; nothing limits it to 33 firings.
Why D tempts people
Milliseconds, not seconds — 33 seconds between frames would be unwatchable.

60. Every graphical program works this way

Real world

The structure of this last program is the structure of essentially every interactive application.

Discussion prompt

Web pages, mobile apps and desktop software all use event handlers and callbacks. Why did this pattern win over programs that drive their own loop?

Hint: How many different things can happen at once?

Answer:

Because an interactive program has many possible next events and no way to predict which comes first — a key, a click, a resize, a timer, a network reply. A loop would have to poll for all of them.

Callbacks invert that: you say what should happen for each kind of event, and the system dispatches whichever actually occurs. A web page's onclick is exactly keyPressed by another name.

And the same constraint follows everywhere: handlers run on the thread that keeps the interface responsive, so a slow one freezes the application. That is why don't block the main thread is the first rule of every UI framework ever written.

61. How sure are you?

Commit first

Commit to an answer and to your confidence.

Predict first

Why must Sprite provide a keyTyped method even though it never uses one?

  • Because KeyListener declares it, and a concrete class must implement every method of every interface it implements
  • Because arrow keys produce keyTyped events
  • Because the Timer calls it
  • Because it is inherited from Canvas

Correct: Because KeyListener declares it, and a concrete class must implement every method of every interface it implements

Why: We don't need the keyTyped method for this example, but it's required by the interface; if we don't provide one, the compiler will complain. So we provide an implementation that does nothing. An interface is all-or-nothing — the same rule as Lesson 16's abstract methods, and the price of the guarantee it gives callers. Arrow keys in fact produce no keyTyped events at all, since they type no character, which is why an empty body here is genuinely correct rather than a shortcut.

62. Explain it to someone else

Explain it

Two minutes, out loud.

Discussion prompt

A classmate's sprite is drawn on screen but does not move when they press the arrow keys, and there is no error. Walk them through what to check, in order.

Hint: Three things have to be true, and the compiler checks only one.

Answer:

First: does the class implement KeyListener and provide all three methods? If not it would not compile, so if it built, that part is fine.

Second: did you call drawing.addKeyListener(sprite)? Implementing the interface says the object can receive events; registering is what makes something send them, and the compiler cannot check that.

Third: drawing.setFocusable(true) — key events only go to the component with keyboard focus. If clicking on the canvas first makes it work, that is the missing line. And check the timer is started, or nothing moves regardless of the keys.

63. Exit ticket

Exit ticket

One question before you close the deck — and the book.

Predict first

What does it mean that a program is event-driven?

  • You write methods and the framework decides when to call them, rather than driving the program with your own loop
  • The program responds only to the keyboard
  • Every method runs on its own thread
  • The program has no main method

Correct: You write methods and the framework decides when to call them, rather than driving the program with your own loop

Why: Every interface in this chapter exists so that something else can call your code: the window system calls paint and keyPressed, the Timer calls actionPerformed, and Drawing calls draw and step on each Actor. That is the inversion — and it is why the while (true) loop that drove every animation from Chapter 15 onward is absent from the final program. The methods still run on a single thread, one at a time, which is why they have to be short; and main is still there, but its job is now to set things up and get out of the way.

64. Draw the whole lesson

Connect it up

One page, from memory — and then close the book.

Draw it

Draw the seven-step trace of one frame: the key event arriving at the sprite, the timer firing, actionPerformed calling drawing.step, Drawing stepping each Actor, the repaint, and paint calling draw. Mark with an arrow which of the seven are calls into your code. Beside it, list the three interfaces and the methods each requires, and the three wiring lines — add, addKeyListener, setFocusable — with the symptom of omitting each. Finish by writing the one sentence that distinguishes a Timer from a while loop.

65. Recap

Recap

Two sections that hand the driving over to the framework — and the end of Think Java.

if you remember one thingit is this
about event-driven codeyou write methods; the framework calls them
about wiringthe compiler checks the interface, not the registration
about the whole bookevery big program is small pieces with clear contracts

Sources

  1. Downey & Mayfield, Think Java, 2nd edition (Green Tea Press / O'Reilly, 2020) — Think Java 2e, Chapter 17 (Advanced Topics), Sections 17.8-17.9, pp. 290-295
  2. The Java Tutorials — Introduction to Event Listeners
  3. Think Java 2e — free online edition and source code

Want this taught 1-on-1? Alexander tutors Java — $55/session, free consultation.

Book on Wyzant · Text (657) 465-8108