Next: Case study: Dictionary. Up: Inner classes. Previous: None.


Inner classes

The problem

Textbook: Section 12.4

When we wrote our little incrementing program earlier, something peculiar happened: We defined actionPerformed() as a public instance method of ListeningExample, even though we don't really think of actionPerformed() as being something you might do to a ListeningExample. That is, it's not as if a main() method might create a ListeningExample and then call actionPerformed() on it.

You might declare a separate listening class for each button. For example, we might define the following.

class QuitListener implements ActionListener {
    public void actionPerformed(ActionEvent evt) {
        System.exit(0); // this makes entire program exit
    }
}
And then, in the ListeningExample constructor method, we would write
        quit_button.addActionListener(new QuitListener());
That works, and it solves the problem of keeping actionPerformed() out of the ListeningExample class.

That is, it works until we look at writing a listener for the increment button, and we see that it needs to know about the text field. We can get around this by making the IncrListener class remember the text field also: We'll pass the text field into the constructor method, and it will copy it into an instance variable.

class IncrListener implements ActionListener {
    private JTextField number_field;

    public IncrListener(JTextField number_field) {
        this.number_field = number_field;
    }

    public void actionPerformed(ActionEvent evt) {
        int i = Integer.parseInt(number_field.getText());
        number_field.setText("" + (i + 1));
    }
}

Once we have these two classes defined, we can write our ExternalListeningGUI class.


import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

class QuitListener implements ActionListener // and so on, as above

class IncrListener implements ActionListener // and so on, as above

public class ExternalListeningGUI extends JFrame {
    JButton incr_button;
    JButton quit_button;
    JTextField number_field;

    public ExternalListeningGUI() {
        incr_button = new JButton("Increment");
        quit_button = new JButton("Quit");
        number_field = new JTextField();

        incr_button.addActionListener(new IncrListener(number_field));
        quit_button.addActionListener(new QuitListener());

        Container contents = getContentPane();
        contents.add(incr_button, BorderLayout.NORTH);
        contents.add(number_field, BorderLayout.CENTER);
        contents.add(quit_button, BorderLayout.SOUTH);
        pack();
    }

    public static void main(String[] args) {
        (new ExternalListeningGUI()).show();
    }
}

The solution

That approach is a bit of a pain, though: We had to go out of our way to make the IncrListener remember things about the instance at hand. Java actually provides an alternative approach, called the inner class. An inner class is a class definition that is nested within another class definition. In the instance methods of the outer class, you can create instances of the inner class. And the inner class will allow you to access instance members of the outer class instance.

It's confusing when described on paper. Basically, each instance of the inner class has an implicit pointer to an instance of the outer class. Whenever your code for the inner class accesses an instance member for the outer class, it goes through the pointer to get to the variable's value.

In practice, it's not so confusing. Here's how I would actually write the ListeningExample. Here, we've made QuitListener and IncrListener be inner classes.


import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class InternalListeningGUI extends JFrame {
    private class QuitListener implements ActionListener {
        public void actionPerformed(ActionEvent evt) {
            System.exit(0); // this makes entire program exit
        }
    }

    private class IncrListener implements ActionListener {
        public void actionPerformed(ActionEvent evt) {
            int i = Integer.parseInt(number_field.getText());
            number_field.setText("" + (i + 1));
        }
    }

    private JButton incr_button;
    private JButton quit_button;
    private JTextField number_field;

    public InternalListeningGUI() {
        incr_button = new JButton("Increment");
        quit_button = new JButton("Quit");
        number_field = new JTextField();

        incr_button.addActionListener(new IncrListener());
        quit_button.addActionListener(new QuitListener());

        Container contents = getContentPane();
        contents.add(incr_button, BorderLayout.NORTH);
        contents.add(number_field, BorderLayout.CENTER);
        contents.add(quit_button, BorderLayout.SOUTH);
        pack();
    }

    public static void main(String[] args) {
        (new InternalListeningGUI()).show();
    }
}
A couple of things to notice about this:


Next: Case study: Dictionary. Up: Inner classes. Previous: None.