By Alexandre Calsavara
Fundamental Java APIs like Abstract Window Toolkit (AWT) and Swing, not to mention Java Media Framework (JMF), Java Platform Debugger Architecture (JPDA), and JavaBeans, depend heavily on events. Events are an integral part of the Java platform. In this article, I'll present the concepts behind event-driven applications and look at various methods of event handling. Then, I'll discuss some generic techniques you can use to simplify the task of handling events.
Event: An action or occurrence, often generated by the user, to which the program might respond—for example, key presses, button clicks, or mouse movements.
Programming with events
Events are not exclusive to the Java platform; they existed before Java and were popularized by graphical user interfaces like Windows and the X Window System. These graphic-intensive systems are driven by the user with events. In contrast, with the traditional programming model, the application controls program flow. This flow is predetermined by the statements and conditions that are coded (Figure A).
|Traditional program flow|
An example of this type of application is a command-line utility. Once the utility is invoked, it performs its function depending just on the commands given and according to the coded logic, independent of any external factors.
Events do not adhere to this traditional approach because they occur outside of program control. When an event happens, the application is notified, causing the execution of a piece of code (Figure B). Usually, this notification involves calling a predefined procedure or function, passing it enough information to identify the event so that the application can perform its logic.
|Program flow with events|
Events may occur at any time with no predefined order, so a different programming model is required. In this model, the application passively waits for events, which in turn determine what and when is executed. Moreover, the application must be coded so that it does not depend on the order of the events.
This programming model defines an event-driven application, such as an interactive application employing a graphical user interface (GUI). It reacts only to user actions (events), and the action it takes depends on the event. Furthermore, the user can press buttons, select options, and type something at any time and in any order. Basically, events are random—they occur at the user’s discretion.
Events and Java
In Java, events are generated by objects. An event is represented by a java.util.EventObject subclass that carries event information. There are subclasses for each kind of event. At a minimum, they hold a reference to the object that generated the event (the source of the event), but each subclass defines additional information appropriate for the event.
For example, a javax.swing.JButton object generates a java.awt.event.ActionEvent when pressed. It creates an instance of the java.awt.event.ActionEvent class, populates it with information about the event, including a reference to itself, and notifies the interested parties about the event.
The notification mechanism involves calling a specific method of any objects that want to be notified about the event and passing the event object as a parameter. These methods are called event handlers. For an object to be notified about an event, it must implement a specific interface and be registered with the source of the event. These objects are called event listeners.
The interface the object must implement depends on the event it wants to receive. There are interfaces for each kind of event, all of them are subinterfaces of the java.util.EventListener interface. Each interface defines one or more methods that are called by the source of the event. Each method receives an event object as a parameter.
Note that it is common for an interface to define more than one method, and several events are often represented using the same class. For example, both key presses and key releases generate events, but the same class, java.awt.event.KeyEvent, is used to represent both. Although the event object always identifies which type of event happened, it is easier if a different method is called for each one so that you don't have to test for it.
So the corresponding interface for java.awt.event.KeyEvent events, java.awt.event.KeyListener, defines a method for key presses, keyPressed, and another for key releases, keyReleased.
Once the object implements the correct interface, it must be registered as a listener of the desired event with the event source. Each class that generates events provides methods to add and remove listeners for each kind of event it generates. Any number of listeners may be registered with a given source. When an event is fired, each one is called in turn.
To see how to implement event listeners, consider the example in Listing A. It contains a class extending javax.swing.Jdialog and it implements a simple dialog box with two buttons labeled Okand Cancel. Notice that the class implements the java.awt.event.ActionListener interface as well.
Events come from two buttons, so the method actionPerformed tests the event source calling the appropriate method depending on the button pressed. Similarly, other types of controls may be handled by implementing the corresponding interface and using the same logic to dispatch the event to a specific method.
We've looked at what can be considered the most traditional and efficient way to implement event handlers, but it can be cumbersome sometimes—especially when you need to handle dozens of controls. Implementing additional interfaces and tests can prove to be a maintenance problem. For this reason, programmers have used alternate solutions to facilitate the dispatching of events. One of the most popular is to use anonymous classes to dispatch the event directly to the correct method (Listing B).
Although this method is convenient and easy to implement, it is not recommended except for very simple cases because a new class is created for each event. If you consider an application with a complex interface, the number of these small anonymous classes grows quickly. This may slow down the user interface because the majority of class loaders load classes on demand from disk. The user can experience a delay when windows and dialog boxes are constructed while the classes are loaded.
Another common approach for handling events is to use the Reflection API to write a generic class that dispatches events to the event handler method. Listing C shows an example of such a class that dispatches java.awt.event.ActionEvent events. It keeps a mapping of methods to call depending on the source of the event. Any instance method can be called, provided that it receives a java.awt.event.ActionEvent object as a parameter and is declared public. Listing D shows a modified code snippet from Listing A using the dispatcher class.
This approach offers the same advantages as the anonymous classes solution but without its drawbacks. New classes to handle other types of events can be easily created using the same technique.
Event-driven programming isn't exclusive to Java, but it's a key component of that platform. It provides the means to develop flexible applications where the user determines program flow. Implementing event handlers in Java is generally straightforward, but it's important to know which approach is best for which situation. In this article, we looked at three of the most common ways to implement event handling: dispatching events to an object, using an anonymous class to dispatch an event directly to the method that handles it, and using reflection to write a generic class that dispatches events to the event handler method.