Inner classes (Section 12.4)
The problem
The solution
Case study: Dictionary
Exercise
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.
And then, in the ListeningExample constructor method, we would writeclass QuitListener implements ActionListener { public void actionPerformed(ActionEvent evt) { System.exit(0); // this makes entire program exit } }
That works, and it solves the problem of keeping actionPerformed() out of the ListeningExample class.quit_button.addActionListener(new QuitListener());
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(); } }
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.
A couple of things to notice about this: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(); } }
To look at a more extended example using inner classes, we'll examine a Dictionary program.
1 import java.awt.*; 2 import java.awt.event.*; 3 import javax.swing.*; 4 public class Dictionary extends JFrame { 5 // private class definitions 6 private class TermMenuItem extends JMenuItem 7 implements ActionListener { 8 private String defn; 9 public TermMenuItem(String term, String defn) { 10 super(term); 11 addActionListener(this); 12 this.defn = defn; 13 } 14 public void actionPerformed(ActionEvent evt) { 15 text_area.setText(defn); 16 } 17 } 18 private class WindowCloser extends WindowAdapter { 19 public void windowClosing(WindowEvent evt) { 20 System.exit(0); 21 } 22 } 23 // private instance variables 24 private JTextArea text_area; 25 // public instance methods 26 public Dictionary() { 27 super("Dictionary"); 28 addWindowListener(new WindowCloser()); 29 // set up menus 30 JMenuBar menubar = new JMenuBar(); 31 JMenu menu = new JMenu("Term"); 32 setJMenuBar(menubar); 33 menubar.add(menu); 34 menu.add(new TermMenuItem("JButton", 35 "A labeled button that can be pressed")); 36 menu.add(new TermMenuItem("JCheckbox", 37 "A box that can be clicked on or off")); 38 menu.add(new TermMenuItem("JLabel", 39 "A component containing only a string")); 40 menu.add(new TermMenuItem("JTextArea", 41 "A multiline area where text can be displayed or edited")); 42 // set up rest of frame 43 text_area = new JTextArea(5, 30); 44 getContentPane().add(text_area); 45 pack(); 46 } 47 // public class methods 48 public static void main(String[] args) { 49 (new Dictionary()).show(); 50 } 51 }
We saw that the WindowListener interface (used in our TemperatureConversion program) obligates a class to define many methods. Usually the program only wants the bodies of most of them to be empty. The libraries defines the WindowAdapter to deal with this.
The WindowAdapter class defines all of the methods required by WindowListener as empty methods. This enables a program to extend the WindowAdapter class, overriding only those methods that are important to the program. This is what is occurring in lines 18-22: We extend the WindowAdapter class (which implements the WindowListener interface) and override the windowClosing method to in fact close the program. Then in line 28, a new instance of this class is created and registered as a window listener for the Dictionary frame.
The JTextArea class implements a blank area in which multiple lines of data can be displayed or entered. It works similarly to JTextField.
Line 30 creates a JMenuBar and line 32 tells the JFrame to use it as the window's menu bar. The JMenuBar is a container suitable for holding JMenus.
Line 31 creates a JMenu (titled ``Term'') and line 33 adds it into the menu bar. A JMenu is a component which is also a container suitable for holding JMenuItems. When the user clicks on a JMenu, the menu showing the menu items pops up.
Lines 34-41 create the JMenuItems (note how the TermMenuItem class extends JMenuItem) and insert them into the JMenu created in line 31. A JMenuItem is a lot like a JButton: The constructor method takes a string, the label of the menu item. And the addActionListener() method associates an action with what should happen when the user clicks on the menu item.
Notice that, in this case, an action listener is associated with the menu item in the subclass's constructor method in line 11.