The Abstract Windowing Toolkit (AWT) and Java Foundation Classes (JFC) use predefined listener interfaces to add behavior to all of their GUI and non-GUI controls. Since event-handling code needs direct access to UI controls that generate events, developers use inner and anonymous classes to implement event-listener interfaces. The JDK 1.1 specification forces compiler implementations to be compatible with the JDK 1.0 class format. For this reason, the compiled units (class files) for inner and anonymous classes tend to consume more bytes than necessary.
This article throws light on the problems inherent in traditional event-handling implementations using AWT/JFC and demonstrates six ways to get around them.
Note: This article assumes that the reader has a thorough understanding of the AWT event model. Experience with the Reflection API is necessary to modify or extend the five utility classes provided in this article.
Event handling in the JDK
Before getting into the guts of the system, let's take a quick peek at the event model mechanism in AWT and JFC.
The event source (typically a UI component) emits events through a predefined listener interface. Any object interested in those events will register an implementation of the listener with the event source through an addXXXListener()
method. Except for DocumentEvent
, the rest of the events derive from the java.util.EventObject
class. This is the essence of the event model; anything else is implementation detail. To get some background on the AWT/JFC event model, see the Resources section at the end of the article.
There are some outstanding issues with current event-handling implementations:
Large UI implementations generally result in many anonymous or inner classes.
Adapter classes are not defined for many event listener interfaces in JFC. In JDK 1.2, there are 49 listeners and only 9 adapters.
Adapters cannot be used if the implementing class has to derive from some other class.
Many developers use object diagrams to understand the interconnections between different types of objects. Many IDEs automatically generate object diagrams from the source code. These diagrams help developers understand applications quickly without getting lost in implementation details. Some tools fail to digest inner classes; others generate too many crisscrossing lines due to those same inner classes. Object diagrams for the UI code are difficult to comprehend due to the many connecting lines between event generators (UI controls), event handlers (inner or anonymous classes), and listener interfaces. Most of the time, after generating the object diagrams, developers go back to remove inner class objects from the diagram to improve clarity.
Some IDEs generate too much boilerplate code to connect GUI components with event handler methods. Large amounts of glue code results in code bloat and large class files, which is a definite concern for applets.
Due to the backward compatibility requirements of compiler specifications, inner and anonymous classes result in a large applet or application footprint.
- Inner classes cannot derive from adapters if they are already deriving from another base class.
Innovative ways to handle events
The new approach introduced in this article attempts to address the issues listed above by using a few magic classes. I will demonstrate how you can connect event sources and event-handling code using any one of the following six methods:
Standard mechanisms:
Using anonymous classes
Using inner classes
Not using inner or anonymous classes
- New alternatives:
EventHandler
connects the event source and target object at the object levelEventListener
connects the event source and target objects at the method level using a predefined listener classGenericListener
connects the event source and target objects at the method level by dynamically creating a custom listener implementation at runtime
To improve readability of the article, I have chosen to present a very simple UI implementation. Let's walk through it using a simple program that displays two toggling buttons in a Frame
. The program also handles the system-close event to dispose of the Frame
. The same example is presented for each of the utility classes listed above. The following three example programs use standard JDK/AWT classes.
ButtonTest1: Using anonymous classes
The following code sample shows how to use anonymous classes to add behavior to buttons and windows. As you can see, action events generated by the buttons are connected to the
bottomButtonAction
method and
topButtonAction
method, respectively. Similarly, the window closing event is handled by calling the
System.exit()
method.
package test.event;
import java.awt.*; import java.awt.event.*;
/** Uses Anonymous Classes to handle events. */ public class ButtonTest1 extends Frame { Button top = new Button("Toggle bottom button"); Button bottom = new Button("Toggle top button");
public ButtonTest1() { super("ButtonTest1 uses Anonymous Classes"); setLayout(new BorderLayout()); add("North", top); add("Center", bottom);
bottom.setEnabled(true); top.setEnabled(false);
bottom.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { bottomButtonAction(e); } });
top.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { topButtonAction(e); } });
this.addWindowListener(new WindowAdapter() { public void windowClosing(ActionEvent e) { System.exit(0); } });
pack(); show(); }
public void topButtonAction(ActionEvent event) { Button b = (Button)event.getSource(); bottom.setEnabled(true); top.setEnabled(false); } public void bottomButtonAction(ActionEvent event) { Button b = (Button)event.getSource(); bottom.setEnabled(false); top.setEnabled(true); } public static void main(String[] args) { new ButtonTest1(); } }
Disadvantages
If the event-handler code happens to contain large code blocks, it reduces the readability of the program. For such cases, this approach is not a good choice.
This approach also generates anonymous inner classes (the total compiled class size is high). In general, the logic behind the event handling should be extremely simple and small so as to avoid maintenance and readability issues.
Advantages
This is a good choice in cases in which the event-handling code is extremely small. This approach improves code readability, as developers can see the event-handler implementation when the listener is added to the UI control.
ButtonTest2: Using inner classes
This example reimplements the same program using inner classes instead of anonymous classes. The following three classes wrap the event-handling code found inside the anonymous classes:
bottomButtonActionHandler
topButtonActionHandler
windowEventHandler
package test.event;
import java.awt.*; import java.awt.event.*;
/** Uses Inner Classes to handle events. */ public class ButtonTest2 extends Frame { Button top = new Button("Toggle bottom button"); Button bottom = new Button("Toggle top button");
public ButtonTest2() { super("ButtonTest2 uses Inner Classes"); setLayout(new BorderLayout()); add("North", top); add("Center", bottom);
bottom.setEnabled(true); top.setEnabled(false);
bottom.addActionListener(new bottomButtonActionHandler()); top.addActionListener(new topButton
()); this.addWindowListener(new window
EventHandler
());
EventHandler
pack(); show(); }
class topButton
implements ActionListener { public void actionPerformed(ActionEvent event) { Button b = (Button)event.getSource(); bottom.setEnabled(true); top.setEnabled(false); } }
EventHandler
class bottomButtonActionHandler implements ActionListener { public void actionPerformed(ActionEvent event) { Button b = (Button)event.getSource(); bottom.setEnabled(false); top.setEnabled(true); } }
class window
extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); } }
EventHandler
public static void main(String[] args) { new ButtonTest2(); } }
Disadvantages
Object diagrams become ugly due to the presence of numerous inner classes and lines connecting them to several classes and interfaces. This approach results in a large applet/application footprint, as was also the case in the previous example.
Advantages
This approach is preferred to the previous method because it scales well to handle more events using inner classes.
ButtonTest3: Not using inner or anonymous classes
In this example, the main class itself implements all listeners for all UI components contained in the container.
package test.event;
import java.awt.*; import java.awt.event.*;
/** Uses Inner Classes to handle events. */ public class ButtonTest3 extends Frame implements ActionListener,WindowListener { Button top = new Button("Toggle bottom button"); Button bottom = new Button("Toggle top button");
public ButtonTest3() { super("ButtonTest3 does not use Anonymous or Inner Classes"); setLayout(new BorderLayout()); add("North", top); add("Center", bottom);
bottom.setEnabled(true); top.setEnabled(false);
bottom.addActionListener(this); top.addActionListener(this); this.addWindowListener(this);
pack(); show(); }
/** This approach has potential to result in huge if-else blocks. */ public void actionPerformed(ActionEvent event) { Button b = (Button)event.getSource(); if(b == bottom) { bottom.setEnabled(false); top.setEnabled(true); } else if (b == top) { bottom.setEnabled(true); top.setEnabled(false); } }
public void windowClosing(WindowEvent e) { System.exit(0); }
public void windowActivated(WindowEvent e){} public void windowClosed(WindowEvent e){} public void windowDeactivated(WindowEvent e){} public void windowDeiconified(WindowEvent e){} public void windowIconified(WindowEvent e){} public void windowOpened(WindowEvent e){}
public static void main(String[] args) { new ButtonTest3(); } }
Disadvantages
Compared to first two approaches, this approach is easy to learn and use. Novice programmers tend to abuse this approach to a great extent as they try to accommodate more event handlers in a single class. This approach uses the
if-else
block. If the UI happens to contain lots of controls, you will end up with a huge
if-else
block. The code results in non-object-oriented spaghetti code and it does not scale well with a number of GUI components. The object diagrams become cluttered, with many lines connecting this class to several interfaces. Also, large UI implementations tend to be difficult to maintain. As the containing class derives from a container class (
Frame
or
Panel
) to contain controls inside, it cannot use any of the adapter classes provided in the JDK. This results in many empty methods as the above example demonstrates.
Advantages
This code does not generate inner or anonymous classes. As a result, the total number of bytes consumed by class files is relatively small compared to the first two approaches.
New ways to handle events
The next three examples use the following utility classes:
EventHandler
EventListener
GenericListener
These utility classes are included in the zip file listed in the Resources section at the end of this article.
The zip file also contains the following classes, which are useful for developing JFC-based user interfaces:
JEventHandler
JEventListener
To improve ease of use, I have defined the above classes in the java.awt.event
and javax.swing.event
packages. This way, you won't need to add new import statements to use the classes provided in this article.
The following API is used to rewrite the example presented earlier:
public boolean EventHandler.addXXXListener( Object eventSource, Object eventTarget);
The EventHandler
class implements this static method to connect event source and target objects together for AWT-based UI controls. The JEventHandler
class derives from the EventHandler
class to implement the same functionality for JFC-based UI controls.
public EventListener (
Object eventTarget,
String sTargetMethod,
String sInterfaceMethod);
The EventListener
class implements this constructor to connect event source and target objects together for AWT-based UI controls. The JEventListener
class derives from the EventListener
class to implement the same functionality for JFC-based UI controls.
public boolean static GenericListener.connect(
Object eventSource,
Object eventTarget,
String sInterfaceMethod,
String sTargetMethod );