Module 10: Java Objects, Part IV:
Inner Classes and Their Use in Handling Events
Events: An Overview
First, what is an event?
- From a programmer's point of view, an
event is
a "notification" that the user has done "something" related
to the GUI interface he or she is presented with.
- Examples include: "pressed a button", "dragged the mouse",
"typed in the letter q" or "re-sized the window".
- Most GUI widgets are interactive (e.g., a button) and
thus, can cause (via the user) events to be generated.
- Programmers generally desire control when events are
generated.
(For example, when a user presses a button, you the
programmer may need to perform some computation).
Where do you find classes related to events?
- java.awt.event: this package has many event-related classes,
especially for commonly used widgets like JButton.
- javax.swing.event: this package has classes for more,
pure-swing events.
Classes related to events:
- Event classes:
A number of classes inherit from the class EventObject.
These are used to pass information about the event that has
just taken place.
- Event listeners:
These are interfaces defined in java.awt.event
that a programmer must implement in order to handle events.
- Event adapters:
These are abstract classes provided for convenient implementation
of listeners. They need not be used.
We have already seen, for example, that a JButton
can generate an ActionEvent that must be handled
by an ActionListener instance.
Let us review this example once more:
- A button is usually created as follows:
JButton b = new JButton ("Quit");
- Next, we need to call the addActionListener
method to pass an instance of ActionListener.
- However, ActionListener is an interface
in java.awt.event with only one method:
public interface ActionListener extends EventListener {
public abstract void actionPerformed (ActionEvent a);
}
- Note: EventListener is an interface with no
methods (defined, strangely enough, in java.util).
- There are several ways to create a class that
implements the interface ActionListener:
- Get the containing JFrame to implement the
methods and then pass this:
b.addActionListener (this); // "this" refers to the frame.
- Create a separate class just to implement the interface.
- Use an anonymous class (We will learn how to do this):
b.addActionListener (
new ActionListener () {
public void actionPerformed (ActionEvent a)
{
// ... do whatever needs to be done ...
}
}
);
- Use other variations of inner classes.
What really happens when a button is pressed?
- The run-time system detects the event and
figures out where the event occured.
- The appropriate event listener method (actionPerformed)
is called and event information (ActionEvent)
is passed to it.
- When you get control, you need to implement whatever needs
to be done in the method actionPerformed.
How to work with Swing events without losing your mind:
- Look up the listener (or listeners) required for your widget.
- Look at examples to see how your widget reacts to events.
- Provide implementations of methods in the listener.
- Pass a listener instance to the widget.
Some general information about "event" classes:
- Examples: ActionEvent, MouseEvent, KeyEvent,
ItemEvent.
- These classes usually contain information about an event
when it occurs.
e.g., "what key was pressed?"
or, "what are the mouse coordinates?"
Next, a tour of some "listeners":
- ActionListener:
- Use this for handling ActionEvent's.
- Definition:
public interface ActionListener extends EventListener {
public abstract void actionPerformed (ActionEvent a);
}
- MouseListener:
- Use this for handling some MouseEvent's.
- Definition:
public interface MouseListener extends EventListener {
public abstract void mouseClicked (MouseEvent m);
public abstract void mouseEntered (MouseEvent m);
public abstract void mouseExited (MouseEvent m);
public abstract void mousePressed (MouseEvent m);
public abstract void mouseReleased (MouseEvent m);
}
- MouseMotionListener:
- Use this for handling MouseEvent's related to
dragged and moving.
- Definition:
public interface MouseMotionListener extends EventListener {
public abstract void mouseDragged (MouseEvent m);
public abstract void mouseMoved (MouseEvent m);
}
- KeyListener:
- Use this to discover which key on the keyboard was
pressed, hit or released.
- Definition:
public interface KeyListener extends EventListener {
public abstract void keyPressed (KeyEvent k);
public abstract void keyReleased (KeyEvent k);
public abstract void keyTyped(KeyEvent k);
}
- ItemListener:
- Use this to handle any ItemEvent.
- Definition:
public interface ItemListener extends EventListener {
public abstract void itemStateChanged (ItemEvent i);
}
- FocusListener:
- Use this to gain control when a component gains or loses
focus.
- Definition:
public interface FocusListener extends EventListener {
public abstract void focusGained (FocusEvent f);
public abstract void focusLost (FocusEvent f);
}
- AdjustmentListener:
- Use this for handling scrollbar changes.
- Definition:
public interface AdjustmentListener extends EventListener {
public abstract void adjustmentValueChanged (AdjustmentEvent a);
}
- WindowListener:
- Use this for events related to top-level windows (e.g., frame).
- Definition:
public interface WindowListener extends EventListener {
public abstract void windowOpened (WindowEvent w);
public abstract void windowClosed (WindowEvent w);
public abstract void windowClosing(WindowEvent w);
public abstract void windowIconified (WindowEvent w);
public abstract void windowDeiconified (WindowEvent w);
public abstract void windowActivated (WindowEvent w);
public abstract void windowDeactivated (WindowEvent w);
}
- TextListener:
- Use this for handling events related to a TextArea:
- Definition:
public interface TextListener extends EventListener {
public abstract void textValueChanged (TextEvent t);
}
Other, less frequently used listeners include:
ComponentListener and ContainerListener.
Note:
- Generally, the information you need related to an event
can be found in the parameter:
- For example, for a mouse event, you need the mouse
location (coordinates).
- You can get the coordinates in the MouseEvent
instance that gets passed to you.
- It is possible to set it up so that multiple event listeners
are called by a single event.
- It is possible to create altogether new events and
perform low-level manipulation of event-handling.
(For advanced programmers).
Nested classes
It is possible to define classes inside top-level
classes. One motivation for introducing this syntax was to improve
event-handling.
There are four types of nested classes:
- Local classes:
- These can be defined in any block of code.
- They are useful, for example, when multiple instances
of a listener are required.
(Only one local class needs to be defined).
- If only one instance is needed, use an anonymous class instead.
- Anonymous classes:
- These can be defined directly inside a new clause.
- Only one instance is created (and thus, no name is needed).
- They are very useful in creating listeners.
- Static member classes:
- These are nested classes, whose nesting
is used primarily for packaging.
- Avoid this construct - the syntax is strange, and you
need to be really careful in using it.
- Instance member classes:
- These are useful if you are building a very large
class that requires several smaller classes, as well
as multiple instances of them.
- Again, the syntax and behavior is complex - avoid using
this construct if possible.
Using a local class to implement a listener
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:
(source file)
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:
- Logically, event handling for different components should
be separate.
(Instead of being in the same actionPerformed() method.)
- As the number of components increase, the code complexity
of such "all-in-one-place" event-handlers increases.
- The frame should really be a frame and not an event listener.
- The this pointer passed to a component's
add-listener method can allow for inadvertent or
improper use of non-listener methods in the frame.
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 INSIDE a method.
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);
// The rest is the same as before ...
}
Note:
- 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:
(source file)
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: code is similar ...
}
} // End of class "NewFrame"
- Observe 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);
}
}
// ...
}
Note:
- 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.
Using a pre-defined adapter class to implement a listener
Next, we will continue our contrived example by printing
something to System.out when a mouse-click occurs.
Again, we will use a local class:
(source file)
public NewFrame (int width, int height)
{
// Set the title and other frame parameters.
this.setTitle ("Two canvas 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 a 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);
// Deal with mouse clicks.
class FrameMouseListener implements MouseListener {
public void mouseClicked (MouseEvent m)
{
System.out.println ("Mouse click!");
}
// Empty methods - to complete interface.
public void mouseEntered (MouseEvent m) {}
public void mouseExited (MouseEvent m) {}
public void mousePressed (MouseEvent m) {}
public void mouseReleased (MouseEvent m) {}
}
// Add to frame.
cPane.addMouseListener (new FrameMouseListener());
// Show the frame.
this.setVisible (true);
}
Recall that, even though we only wanted to handle mouseClicked
we still had to provide (empty) implementations for
other mouse methods like mouseDragged:
class FrameMouseListener implements MouseListener {
// ...
public void mouseClicked (MouseEvent m)
{
// ...
}
// Empty methods - to complete interface.
public void mouseEntered (MouseEvent m) {}
public void mouseExited (MouseEvent m) {}
public void mousePressed (MouseEvent m) {}
public void mouseReleased (MouseEvent m) {}
}
Java often provides special "adapter" classes to relieve this effort:
- You don't really need to use these adapters if you don't
want to.
- The adapter classes simply provide empty implementations
of all methods.
- For example, MouseAdapter provides an empty
implementation of all methods in MouseListener.
- We will re-write our CanvasMouseListener by making
it extend the MouseAdapter class:
(source file)
// Deal with mouse clicks.
class FrameMouseListener extends MouseAdapter {
public void mouseClicked (MouseEvent m)
{
System.out.println ("Mouse click!");
}
// Don't need these:
// public void mouseEntered (MouseEvent m) {}
// public void mouseExited (MouseEvent m) {}
// public void mousePressed (MouseEvent m) {}
// public void mouseReleased (MouseEvent m) {}
}
- Thus, one advantage of using local classes is that you
can extend adapters as shown above.
(We would not be able to do this with NewFrame
since it already extends JFrame).
Using an anonymous class to implement a listener
Recall the listener for the "quit" button defined earlier:
class NewFrame extends JFrame {
// ...
// Constructor.
public NewFrame (int width, int height)
{
// ...
// Create a local class for the quit button.
class QuitActionListener implements ActionListener {
public void actionPerformed (ActionEvent a)
{
System.exit(0);
}
}
// Pass an instance to the button.
quitB.addActionListener (new QuitActionListener());
// ...
}
}
Here, we defined a class and used it only once, wasting
a name.
Java provides the ability to define a class right where it's
needed - in creating an instance:
(source file)
class NewFrame extends JFrame {
// ...
public NewFrame (int width, int height)
{
// ...
// Quit button
quitB = new JButton ("Quit");
quitB.setBackground (Color.red);
// Create an implementation right in the method call:
quitB.addActionListener (
new ActionListener() {
public void actionPerformed (ActionEvent a)
{
System.exit (0);
}
}
);
cPane.add (quitB);
// ...
}
} // End of class "NewFrame"
Note:
- The class is defined in the parameter list of a method!
- The syntax is a little strange.
- You can think of it this way:
quitb.addActionListener (
// This is the parameter of addActionListener:
new ActionListener() // A new instance of ActionListener.
// This is the body of the class:
{
public void actionPerformed (ActionEvent a)
{
System.exit(0);
}
}
); // End of parameter list.
- Stranger still is the fact that ActionListener
is an interface.
- Thus, the "anonymous" body above provides an implementation
of the interface ActionListener.
- You can extend an existing class with the
same syntax, for example:
(single canvas - source file)
// Deal with mouse clicks.
cPane.addMouseListener (
new MouseAdapter () {
public void mouseClicked (MouseEvent m)
{
System.out.println ("Mouse click!");
}
}
);
Exercise 10.1:
Recall that we implemented an Enumeration for
linked lists by having the linked list class implement the
Enumeration interface:
- The definition of the Enumeration interface:
public interface Enumeration {
public abstract boolean hasMoreElements();
public abstract Object nextElement();
}
- The class LinkedList itself implemented the
Enumeration interface:
class LinkedList implements Enumeration {
// ...
public boolean hasMoreElements ()
{
// ...
}
public Object nextElement()
{
// ...
}
public Enumeration getEnumeration ()
{
// ...
}
} // End of class "LinkedList"
In this exercise, change the above code
so that LinkedList does not implement Enumeration.
Instead, in the method getEnumeration, create an
anonymous class that does the job right in the return
statement.
Esoteric topics: static and instance member classes
For completeness,
we will now consider the remaining two kinds of inner classes:
- Static member classes.
- Instance member classes.
NOTE: Both of these have behavior and syntax that can be difficult to understand.
If possible, avoid using them.
We will present the material via examples:
- Static member classes:
- Example: (source file)
class A {
// A static member class.
static class B {
int x; // Data.
public B (int i) // Constructor.
{
x = i;
}
public void print () // A method.
{
System.out.println ("B: x=" + x);
}
}
}
public class TestStaticLocal {
public static void main (String[] argv)
{
// Create an instance of B.
A.B b = new A.B (1);
b.print(); // Prints "1".
// Create another instance of B.
A.B b2 = new A.B (2);
b2.print(); // Prints "2".
}
}
- Note:
- The class B is defined inside class A.
- B is a static member of A.
- The only reference to B is in the form A.B.
- Thus, the only purpose is "packaging".
- If you are building a large class and need to package
together many related classes, this is one way of doing it.
- That way, name-clashes can be avoided.
- For example, if B is a really popular class name,
then, by packaging it inside other classes, different B's
can be differentiated.
- Instance member classes:
- In the above example, if the static keyword is
removed, the code will not compile:
(source file)
class A {
class B {
int x; // Data.
public B (int i) // Constructor.
{
x = i;
}
public void print () // A method.
{
System.out.println ("B: x=" + x);
}
}
}
public class TestLocal {
public static void main (String[] argv)
{
A.B b = new A.B (1); // Does not compile.
b.print();
}
}
- Here, we wanted to create an instance of B
since B is not static anymore.
- However, you need to create an instance of A
before you can create an instance of B:
(source file)
class A {
// Instance data in A
int y;
// Constructor for A.
public A (int y)
{
this.y = y;
}
// Instance member class:
class B {
int x; // Data.
public B (int i) { // Constructor.
x = i;
}
public void print () // A method.
{
System.out.println ("B: x=" + x + ", y=" + y);
}
}
}
public class TestLocal2 {
public static void main (String[] argv)
{
// Create an instance of A first.
A a = new A (1);
// Now create an "associated" instance of B.
// Note the strange syntax!
A.B b = a.new B (2);
b.print(); // Prints "B: x=2, y=1"
}
}
- Such an instance member class is always associated with
an instance of the containing class.
- Instance member classes have strange syntax and behavior,
and should be used with care.
- This feature is useful when building a very large class.
(Some library classes like Component are probably very large).
- Inside a large class, you may want to re-use instances of
of a smaller class. Then, to avoid name clashes, you can use
the above construct.
- Member classes can be nested to any depth, but such nesting
is discouraged and ought to be used with extreme care if at all.
© 1998, Rahul Simha (revised 2017)