SEN1002 · Week 6 · GUI and Event-driven Programming

Event-driven programming: listeners, anonymous classes, lambdas

What happens when a button is clicked: the event source, the listener interface, registration, getSource, and writing the handler as an inner class, an anonymous class or a lambda.

event-driven programmingeventevent sourceevent listenerevent handlerActionListenerActionEventactionPerformedaddActionListenerinner classanonymous inner classlambda expressionfunctional interfaceadapter classeffectively final

The programs you have written so far ran from start to finish in order. A GUI program, on the other hand, waits after opening the window: when the user clicks a button, presses a key or moves the mouse, the related code runs. This is called event-driven programming. In the exam this topic brings “write a handler” questions, converting an anonymous class to a lambda, and compile errors (effectively final, missing public, wrong signature).

Golden rules:

  1. Event source, event object, listener. When a button is clicked, Swing creates an ActionEvent and calls the actionPerformed method of every ActionListener registered on the button.
  2. Registration is required. If you write a listener and do not attach it with addActionListener, nothing happens, and there is no error either.
  3. A lambda works only with a single-method interface. ActionListener yes, MouseListener no.
  4. A lambda and an anonymous class cannot modify a local variable. Make anything that will change a field.

The event model

PartIts jobExample
Event sourceThe component that produces the eventJButton, JTextField (Enter), JCheckBox
Event objectThe object that carries what happenedActionEvent, ItemEvent, MouseEvent, KeyEvent
Event listenerThe object that reacts to the eventActionListener, ItemListener, MouseListener
Event handlerThe listener’s method that is calledactionPerformed(ActionEvent e)

The listeners you will use:

InterfaceWhen?Method(s)Registration
ActionListenerA button is clicked, Enter in a text fieldactionPerformed(ActionEvent)addActionListener
ItemListenerA checkbox / radio button is selected or deselecteditemStateChanged(ItemEvent)addItemListener
MouseListenerClick, press, release, enter, exit5 methodsaddMouseListener
MouseMotionListenerMouse movement, dragging2 methodsaddMouseMotionListener
KeyListenerA key press3 methodsaddKeyListener

All of them are in the java.awt.event package: do not forget to write import java.awt.event.*;.

Four ways to write a handler

1. Inner class

A class defined inside another class that is not static. Its biggest advantage: it accesses the outer object’s private fields directly.

public class CounterPanel extends JPanel {
    private int count = 0;
    private final JButton button = new JButton("Click");
    private final JLabel label = new JLabel("Count: 0");

    public CounterPanel() {
        add(button);
        add(label);
        button.addActionListener(new ClickHandler());   // registration
    }

    private class ClickHandler implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            count++;                                     // field of the outer class
            label.setText("Count: " + count);
        }
    }
}

After three clicks:

Count: 3

2. The class itself is the listener

The class says implements ActionListener and registers itself with addActionListener(this). Several buttons share the same method; e.getSource() tells which one was clicked.

public class CalcPanel extends JPanel implements ActionListener {
    private final JTextField a = new JTextField("8", 4), b = new JTextField("2", 4);
    private final JButton plus = new JButton("+"), div = new JButton("/");
    private final JLabel result = new JLabel("=");

    public CalcPanel() {
        add(a); add(b); add(plus); add(div); add(result);
        plus.addActionListener(this);
        div.addActionListener(this);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        int x = Integer.parseInt(a.getText());
        int y = Integer.parseInt(b.getText());
        if (e.getSource() == plus) result.setText("= " + (x + y));
        else if (e.getSource() == div) result.setText("= " + (x / y));
        System.out.println("command=" + e.getActionCommand() + " " + result.getText());
    }
}

When + and then / are clicked, and then b is set to “0” and / is clicked again:

command=+ = 10
command=/ = 4
ArithmeticException: / by zero

The handler did not print the last line: the exception that was not caught inside the handler came out, and in the test run the try/catch around the doClick() call caught it. In a real window this exception goes up to Swing’s event thread and a stack trace is printed to the console. That is why you use try/catch inside a handler (see the TemperatureFrame example below).

3. Anonymous inner class

A class with no name, of which a single object is created at the place where it is written. It has the form new InterfaceName() { ...methods... }. You cannot new an interface, but if you add a body, that body becomes a nameless class that implements the interface.

JButton hello = new JButton("Hello");
JLabel msg = new JLabel();
hello.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        msg.setText("Hi, " + e.getActionCommand());
    }
});

msg.getText() after a click:

Hi, Hello

4. Lambda expression

An interface with a single abstract method is called a functional interface. ActionListener is one. In that case, instead of the whole template of the anonymous class, you write only the parameter and the body:

hello.addActionListener(e -> msg.setText("Hi, " + e.getActionCommand()));
Anonymous classLambda
new ActionListener() {(none)
public void actionPerformed(ActionEvent e) {e ->
msg.setText(...);msg.setText(...)
} }););

If the body has more than one line, braces are used: e -> { line1; line2; }.

Captured variables and this

Anonymous classes and lambdas can use local variables from outside but cannot modify them. The local variable they use must be final or effectively final (never reassigned).

int count = 0;
b.addActionListener(e -> count++);   // DOES NOT COMPILE

local variables referenced from a lambda expression must be final or effectively final. Fields (instance variables) are not subject to this rule; use a field for counters, totals and state information.

this means different things in the two:

Anonymous classLambda
thisThe anonymous class’s objectThe outer object
Access to the outer objectOuterClass.thisDirectly this
Inside Outer.wire(), Outer.toString() = "Outer":
  lambda:     this                            → Outer
  anonymous:  this.toString().equals("Outer") → false
  anonymous:  Outer.this                      → Outer

Mouse and adapter classes

MouseListener has five methods. So that you do not have to write all five when you are only interested in clicks, Java provides ready-made adapter classes: MouseAdapter, KeyAdapter, WindowAdapter. They implement all the methods of the interface with empty bodies; you override only what you want. Since an adapter is a class, it is used with an anonymous class, not a lambda.

JPanel area = new JPanel();
area.addMouseListener(new MouseAdapter() {
    @Override
    public void mouseClicked(MouseEvent e) {
        System.out.println("clicked at " + e.getX() + "," + e.getY());
    }
});

When the point (40, 25) is clicked:

clicked at 40,25

Checkbox and other events

JCheckBox and JRadioButton produce an ItemEvent when the selection changes. With e.getStateChange() == ItemEvent.SELECTED you learn whether it was checked; or directly with checkBox.isSelected().

JCheckBox bold = new JCheckBox("Bold");
bold.addItemListener(e -> System.out.println("bold=" + (e.getStateChange() == ItemEvent.SELECTED)));
// clicked twice
bold=true
bold=false

Other useful methods:

  • button.setEnabled(false): the component turns gray and produces no events. In the test run, doClick() on a disabled button did not run the listener.
  • button.removeActionListener(l): removes the registration. For this you need to keep the listener in a variable.
  • textField.addActionListener(...): runs when Enter is pressed in the text field.

PITFALL · the order of several listeners. If you register two listeners on a button, both run. The Java documentation does not guarantee in which order they are called. In the test run (JDK 26) the last registered ran first: second registered, then first registered. In the exam, do not write code that depends on the order; do the two jobs in order inside a single handler.

Building a window from start to finish

Let’s add events to last week’s TemperatureFrame example. The order: define the components as fields, place them in the constructor, attach the listener at the end.

public class TemperatureFrame extends JFrame {
    private final JTextField celsiusField = new JTextField(6);
    private final JButton convertButton = new JButton("Convert");
    private final JLabel resultLabel = new JLabel("F: ?");

    public TemperatureFrame() {
        super("C to F");
        setLayout(new FlowLayout());
        add(new JLabel("Celsius:"));
        add(celsiusField);
        add(convertButton);
        add(resultLabel);

        convertButton.addActionListener(e -> {
            try {
                double c = Double.parseDouble(celsiusField.getText().trim());
                double f = c * 9 / 5 + 32;
                resultLabel.setText("F: " + f);
            } catch (NumberFormatException ex) {
                resultLabel.setText("Invalid number");
            }
        });

        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(300, 100);
    }
}

When Convert is pressed with the value “100”, then ” 37 ”, then “abc”, the label shows:

F: 212.0
F: 98.6
Invalid number

The lambda parameter is e and the variable in the catch is ex. If you had used the same name (e), you would get a compile error: the lambda parameter is already defined in that scope.

Common mistakes

Wrong ideaThe truth
I wrote the listener class, the button worksIf you do not register it with addActionListener, nothing happens
actionPerformed() can be written without a parameterThe signature is actionPerformed(ActionEvent e), and public
new ActionListener()It is an interface; add a body (anonymous) or write a lambda
count++ (local) inside a lambdaEffectively final rule; use a field
Every listener can be a lambdaOnly single-method interfaces; MouseAdapter for MouseListener
In an anonymous class this is the outer objectIt is the anonymous object; the outer object is Outer.this
I call actionPerformed myselfSwing calls it, you only register
e.getSource().equals("+")getSource returns an object: e.getSource() == plus or getActionCommand()
Lambda parameter e, and e in the catch tooYou cannot have two e in the same scope; use ex in the catch

Test yourself

Exam-style questions. Solve them on paper first, then reveal the answer.

Question 1easy

What is printed after the button is clicked three times?

class CounterPanel extends JPanel {
    private int count = 0;
    private JButton button = new JButton("Click");
    private JLabel label = new JLabel("Count: 0");
    CounterPanel() {
        add(button); add(label);
        button.addActionListener(new ClickHandler());
    }
    private class ClickHandler implements ActionListener {
        public void actionPerformed(ActionEvent e) {
            count++;
            label.setText("Count: " + count);
        }
    }
}
// after 3 clicks: System.out.println(label.getText());
Show answer
Count: 3

ClickHandler is an inner class; it accesses the outer object's count and label fields directly. On every click actionPerformed runs once: count becomes 1, 2, 3, and the label shows "Count: 3".

Question 2medium

Find the compile error.

JButton b = new JButton("+1");
int count = 0;
b.addActionListener(e -> count++);
Show answer
A local variable cannot be modified inside a lambda (it is not effectively final)

Lambdas and anonymous classes can only read local variables from outside, and that variable must be final or effectively final. javac: local variables referenced from a lambda expression must be final or effectively final. Fix: make the counter a field of the class (private int count;); fields are not subject to this rule.

Question 3medium

Find the compile error.

class Handler implements ActionListener {
    public void actionPerformed() {
        System.out.println("clicked");
    }
}
Show answer
The actionPerformed(ActionEvent e) parameter is missing

The method in the interface is void actionPerformed(ActionEvent e). The version without parameters becomes a separate method (an overload), and the interface's method stays unimplemented. javac: Handler is not abstract and does not override abstract method actionPerformed(ActionEvent) in ActionListener.

Question 4hard

Find the compile error.

JPanel p = new JPanel();
p.addMouseListener(e -> System.out.println("click"));
Show answer
MouseListener is not a functional interface, a lambda cannot be written

A lambda works only with interfaces that have a single abstract method (functional interfaces). MouseListener has five methods (mouseClicked, mousePressed, mouseReleased, mouseEntered, mouseExited). javac: MouseListener is not a functional interface. Fix: new MouseAdapter() { public void mouseClicked(MouseEvent e) { ... } }.

Question 5medium

What is printed?

// a = "8", b = "2"; both buttons call this same handler
public void actionPerformed(ActionEvent e) {
    int x = Integer.parseInt(a.getText());
    int y = Integer.parseInt(b.getText());
    if (e.getSource() == plus) result.setText("= " + (x + y));
    else if (e.getSource() == div) result.setText("= " + (x / y));
    System.out.println("command=" + e.getActionCommand() + " " + result.getText());
}
// user clicks "+" then "/"
Show answer
command=+ = 10 / command=/ = 4

getSource() returns the object that triggered the event, and you tell which button it was with ==. getActionCommand() is by default the text of the button. +: 8 + 2 = 10. /: 8 / 2 = 4 (int division). If b were "0", the / button would throw ArithmeticException: / by zero.

Question 6easy

Find the compile error.

JButton b = new JButton("ok");
b.addActionListener(new ActionListener());
Show answer
ActionListener is an interface, it cannot be created directly with new

javac: ActionListener is abstract; cannot be instantiated. You either write a class and create that, or add an anonymous class body (new ActionListener() { public void actionPerformed(ActionEvent e) { ... } }), or use a lambda.

Question 7easy

Rewrite this anonymous class as a lambda expression.

hello.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        msg.setText("Hi, " + e.getActionCommand());
    }
});
Show answer
hello.addActionListener(e -> msg.setText("Hi, " + e.getActionCommand()));

ActionListener has a single method, so a lambda can be written. The parameter list (e), the arrow, the body. Since it is a single expression, no braces or return are needed. When the button is clicked, the label becomes Hi, Hello.

Question 8hard

Inside an instance method of class Outer (whose toString() returns "Outer"), what does this refer to (a) inside an anonymous ActionListener and (b) inside a lambda listener?

Show answer
(a) the anonymous class's object, (b) the Outer object

An anonymous class is a new class, and this inside it is the object of that class; for the outer object you write Outer.this. A lambda does not open a new class, and this is the outer object. In the test run, the lambda printed Outer for this, in the anonymous class this.toString().equals("Outer") came out false, and Outer.this printed Outer.

Question 9medium

A button's listener is registered, then button.setEnabled(false); is called and button.doClick(); runs. Is the listener called?

Show answer
No

A disabled component does not produce events; doClick does not click either. In the test run the listener's message was not printed, only the following line ran. This is the standard way to stop a user from repeating an action.

Question 10easy

Which statement is correct about the three steps of event handling?

  1. Create the event object, call actionPerformed yourself, then repaint
  2. Write a class implementing the listener interface, create an object of it, register it with the source using addXxxListener
  3. Extend JButton and override its click() method
Show answer
Write a class implementing the listener interface, create an object of it, register it with the source using addXxxListener

Swing creates the event object and Swing calls actionPerformed; you do not call it. Your job: (1) a class that implements the listener interface (or an anonymous class / lambda), (2) an object of it, (3) registering it with the source using addActionListener. If you forget to register, nothing happens, and there is no error either.

Question 11medium

Write the event handling for TemperatureFrame (fields celsiusField, convertButton, resultLabel): when Convert is clicked, read the Celsius value, compute F = C * 9 / 5 + 32 and show F: 212.0. If the text is not a number, show Invalid number. Use a lambda.

Show answer
convertButton.addActionListener(e -> { try { ... } catch (NumberFormatException ex) { ... } });

It is added at the end of the constructor. Accessing fields from inside a lambda is allowed (only local variables must be effectively final). trim() removes whitespace at the edges:

convertButton.addActionListener(e -> {
    try {
        double c = Double.parseDouble(celsiusField.getText().trim());
        double f = c * 9 / 5 + 32;
        resultLabel.setText("F: " + f);
    } catch (NumberFormatException ex) {
        resultLabel.setText("Invalid number");
    }
});
In the test run: "100" → F: 212.0, " 37 " → F: 98.6, "abc" → Invalid number. Since c is a double, c * 9 / 5 is a double operation; if C were an int, the order of 9 / 5 would matter.

Verification: Events were triggered with doClick() in headless mode without opening a window (java -Djava.awt.headless=true verify/SEN1002/W06.java and W06b.java). Compile errors were tried with javac (verify/SEN1002/err/E06a-e.java).

Review card

Event-driven: the program does not run in sequence, it reacts to whatever the user does. 3 parts: source (JButton) → event object (ActionEvent) → listener (ActionListener). 3 steps: write the listener → create its object → source.addActionListener(listener). ActionListener: one method public void actionPerformed(ActionEvent e) (public REQUIRED). e.getSource() → the object that caused the event (compare with ==). e.getActionCommand() → the button's text. 4 ways to write a handler: separate class, inner class, anonymous class, lambda. An inner class accesses the outer object's private fields. Anonymous: new ActionListener() { public void actionPerformed(ActionEvent e) { ... } } Lambda: b.addActionListener(e -> label.setText("hi")); only for single-method interfaces. LOCAL variable in lambda/anonymous: read only, effectively final. Counter → make it a field. In an anonymous class this = the anonymous object (for the outer one, Outer.this). In a lambda this = the outer object. MouseListener has 5 methods → NO lambda → derive from MouseAdapter, override what you want. JCheckBox: ItemListener / ItemEvent.SELECTED. setEnabled(false) → no events.