We will use a somewhat contrived (but simple!) example to illustrate:
In addition to a quit button, we'll put up two buttons, "hello" and "world". We will respond to a button-click by writing to System.out.println(). First, we will implement "listeners" the way have have before: by having the frame implement listener interfaces. Then, later, we will use local classes. Here is what we want the frame to look like:
Here is the code:
import java.awt.*; import javax.swing.*; import java.awt.event.*; class NewFrame extends JFrame implements ActionListener { // Data. JButton quitB; // Quit button. JButton helloB, worldB; // Two silly buttons. // Constructor. public NewFrame (int width, int height) { // Set the title and other frame parameters. this.setTitle ("Two button example"); this.setResizable (true); this.setSize (width, height); // We'll use a flowlayout Container cPane = this.getContentPane(); cPane.setLayout (new FlowLayout()); // Quit button quitB = new JButton ("Quit"); quitB.setBackground (Color.red); quitB.addActionListener (this); cPane.add (quitB); // "Hello" button helloB = new JButton ("Hello"); helloB.setBackground (Color.green); helloB.addActionListener (this); cPane.add (helloB); // "World" button worldB = new JButton ("World"); worldB.setBackground (Color.green); worldB.addActionListener (this); cPane.add (worldB); // Show the frame. this.setVisible (true); } // Need this method for the ActionListener interface. public void actionPerformed (ActionEvent a) { // Get the button string. String s = a.getActionCommand(); if (s.equalsIgnoreCase ("Quit")) System.exit(0); else if (s.equalsIgnoreCase ("Hello")) { System.out.print ("Hello "); } else if (s.equalsIgnoreCase ("World")) { System.out.println ("World!"); } } } // End of class "NewFrame" public class TwoButton { public static void main (String[] argv) { NewFrame nf = new NewFrame (300, 200); } }
The above approach of having the frame implement the listeners is undesirable for many reasons:
To solve this problem, Java provides the use of so-called local classes:
In the example below, we will use a local class for the ActionListener of the quit button. Since all the relevant code is in the constructor of NewFrame only the constructor will be shown. Note the unusual syntax and the fact that a class is being defined in a body of code. Here is an implementation using a local class for one of the button-listeners: (source file)
public NewFrame (int width, int height) { // Set the title and other frame parameters. this.setTitle ("Two button example"); this.setResizable (true); this.setSize (width, height); // We'll use a flowlayout Container cPane = this.getContentPane(); cPane.setLayout (new FlowLayout()); // Quit button quitB = new JButton ("Quit"); quitB.setBackground (Color.red); // Local class definition. class QuitButtonListener implements ActionListener { public void actionPerformed (ActionEvent a) { System.exit (0); } } // Instantiate local class. QuitButtonListener qListener = new QuitButtonListener(); // Add the listener to the button. quitB.addActionListener (qListener); // Finally, add the button to the frame. cPane.add (quitB); // "Hello" button helloB = new JButton ("Hello"); helloB.setBackground (Color.green); helloB.addActionListener (this); cPane.add (helloB); // "World" button worldB = new JButton ("World"); worldB.setBackground (Color.green); worldB.addActionListener (this); cPane.add (worldB); // Show the frame. this.setVisible (true); }
Observe the scope of the class QuitActionListener:
class NewFrame extends JFrame implements ActionListener { // ... // Constructor. public NewFrame (int width, int height) { // ... // Create a local class. QuitActionListener is our name. class QuitActionListener implements ActionListener { public void actionPerformed (ActionEvent a) { System.exit(0); // Action required for quit. } } // ... } }
Since the button must be passed an instance, we create one:
// Instantiate local class. QuitActionListener qListener = new QuitActionListener(); // Add the listener to the button. quitB.addActionListener (qListener);We can shorten this code:
quitb.addActionListener (new QuitActionListener());Next, we will make some modifications:
We will use local classes for each button. We will use variables to hold the output strings, to highlight an important feature. Here is the code:
class NewFrame extends JFrame { // Note: Frame does not implement ActionListener anymore // Data. JButton quitB; // Quit button. JButton helloB, worldB; // Two silly buttons. String helloStr = "Hello "; String worldStr = "World!"; // Constructor. public NewFrame (int width, int height) { // Set the title and other frame parameters. this.setTitle ("Two button example"); this.setResizable (true); this.setSize (width, height); // We'll use a flowlayout Container cPane = this.getContentPane(); cPane.setLayout (new FlowLayout()); // Quit button quitB = new JButton ("Quit"); quitB.setBackground (Color.red); // Local listener: class QuitActionListener implements ActionListener { public void actionPerformed (ActionEvent a) { System.exit (0); } } quitB.addActionListener (new QuitActionListener()); cPane.add (quitB); // "Hello" button helloB = new JButton ("Hello"); helloB.setBackground (Color.green); // Listener: class HelloActionListener implements ActionListener { public void actionPerformed (ActionEvent a) { // Note: we are accessing top-level variable. System.out.print (helloStr); } } helloB.addActionListener (new HelloActionListener()); cPane.add (helloB); // "World" button worldB = new JButton ("World"); worldB.setBackground (Color.green); // Listener: class WorldActionListener implements ActionListener { public void actionPerformed (ActionEvent a) { System.out.print (worldStr); } } worldB.addActionListener (new WorldActionListener()); cPane.add (worldB); // Show the frame. this.setVisible (true); } } // End of class "NewFrame"Obs erve how a top-level variable like helloStr can be accessed inside the inner class:
class NewFrame extends JFrame { // ... String helloStr = "Hello "; String worldStr = "World!"; public NewFrame (int width, int height) { // ... class HelloActionListener implements ActionListener { public void actionPerformed (ActionEvent a) { // Note: we are accessing top-level variable. System.out.print (helloStr); } } // ... }
Only top-level (heap) variables can be referenced in local classes, as above. Method (local) variables and parameters that are on the stack cannot be referenced.