by Mark Johnson

“Keep listening for upcoming events”

how-to
Oct 1, 199725 mins

How to wire JavaBeans together using "event listeners"

In Douglas Adams’s The Hitchhiker’s Guide to the Galaxy, Arthur Dent has a problem:

“You know,” said Arthur, “it’s at times like this, when I’m trapped in a Vogon airlock with a man from Betelgeuse, and about to die of asphyxiation in deep space, that I really wish I’d listened to what my mother told me when I was young.”

“Why, what did she tell you?”

“I don’t know, I didn’t listen.”

JavaBeans can be configured to “listen” to other software objects. And as you’ll see, a Java 1.1 class (including any JavaBean) can listen not only to its parent, but also to any class that produces events by becoming an event listener. The event listener idea is central to how Java classes (and other JavaBeans) handle events.

Last month we discussed a specific type of event listener: the PropertyChangeListener interface that takes action when other Beans’ properties change. This month we’ll take a closer look at the whole concept of an “event listener.” You’ll see how event listeners are used in the new AWT. (When I say “new,” I mean since JDK 1.1 was released.) We’ll talk about how to define your own event types, and then make those new event types visible to other classes. Then, as an example, we’ll extend the BarChartBean, creating a new event type and then using it to wire the BarChartBean to another Bean. We’ll go over some details about how to write event listener interfaces, using the AWT as an example, and conclude with a discussion of inner classes, a new Java 1.1 language feature.

I’d also like to introduce two new icons that will help identify key points:

The JavaBean icon is a key concept for JavaBeans.
And the cuppajoe icon indicates new or key ideas specific to the Java language.

What’s an event?

A software event is a piece of data that indicates that something has occurred. Maybe a user moved the mouse. Maybe a datagram just arrived from the network. Maybe a sensor has detected that someone’s just come in the back door. All of these occurrences can be modeled as events, and information about what happened can be included in the event. Often it’s convenient to write software systems in terms of event processing: Programming then becomes a process of specifying “when this happens, do that.” If the mouse moved, move the cursor with it; if a datagram arrived, read it; if there’s an intruder, play the recording of Roseanne releasing the rabid Rottweiler.

Usually, an event contains information about the event source (the object that created or first received the event), when the event occurred, and some subclass-specific information that the event receiver can use to figure out what happened and what to do. In a windowing system, for example, there might be a subtype of event for mouse clicks. The mouse click event would include the time the click occurred, and also might include such information as where on the screen the mouse was clicked, the state of the SHIFT and ALT buttons, and an indication of which mouse button was clicked. The code that handles the event is called, strangely enough, the event handler.

So, what does this have to do with JavaBeans? Events are the primary way that Beans communicate with each other. As we’ll see below, if you choose events and their connections judiciously, you can interconnect Beans in your application, tell each Bean what to do in response to the events it cares about, and the application simply behaves. Each Bean minds its own business, responding appropriately to incoming events, and sending new events to interested neighboring Beans as new situations occur. Once you understand how to use events, you can write Beans that use them to communicate with other components. And what’s more, external systems like Integrated Development Environments (IDEs) automatically detect the events your Beans use and let you interconnect Beans graphically. IDEs also can send events to and receive events from JavaBeans, essentially controlling them from the outside.

In order to understand how events work with Beans, though, you’ve got to understand how they work in Java. And events work differently now that JDK 1.1 is the standard.

Old News: What’s wrong with JDK 1.0 events?

In the Java Developer’s Kit (JDK) 1.0, events were used primarily in the Abstract Windowing Toolkit (AWT) to tell classes when something happened in the user interface. Programmers used inheritance to process events by subclassing an object that could receive that event type and overriding how the parent class processed the event.

For example, in Java version 1.0, the only way to get an Action event was to inherit from some other class that already knew how to handle Action events:

public class MyButton extends java.awt.Button
{
    // Override action() method to handle action event
    public boolean action(Event evt, Object what) {
        // Now do something with the action event
    }
}

This means that only classes that inherited from java.awt.Button could do anything to respond to a button click. This structure left Java events tied to the user interface and inflexible. It wasn’t easy to create new event types. And even if you could create new events, it was hard to change the events that a class could respond to, because that information was hard-coded into the “family tree” (the inheritance graph) of the AWT.

The new JDK 1.1 has a more general event structure, which allows classes that produce events to interact with other classes that don’t. Instead of defining event-processing methods that client subclasses must override, the new model defines interfaces that any class may implement if it wants to receive a particular message type. (You may hear this referred to as processing events by delegation instead of by inheritance.) Let’s continue with this JDK 1.0 button example and move toward the JDK 1.1 model.

Let’s say I wanted to create a new class that does something when a button is pressed. In 1.0, I’d have to subclass java.awt.Button to handle the Button action event, and then that button would somehow notify my new class that the button press had occurred:

//... elsewhere in the program we define the object that "listens"
// for button actions.
ActionListener myActionListener = new ActionListener();
//...
// Button action event
public class MyButton extends java.awt.Button
{
    // Override action() to notify my new class
    public boolean action(Event evt, Object object)
    {       myActionListener.action(evt, object);
    }
}

Now the object myActionListener receives an event every time any MyButton is pushed. myActionListener is not necessarily a subclass of java.awt.Component, but it does contain an action() method. We call the new class an ActionListener because, seen from the new class’s point of view, it is “listening” for action events on the button it is “attached” to. There are still some problems, though:

  • The object to notify when this button gets pushed is hard-coded, meaning I can’t “rewire” the association between the Button and the myActionListener at runtime.

  • Only a single other object gets notified: What if several other objects have an interest in the button press?

  • We still haven’t solved the problem that receiving a button action event is inherited — meaning that myActionListener must inherit from some base class that “knows” about buttons and their action events.

A solution to the first problem might be to add to MyButton the methods setListener(ActionListener newListener) and myNewClass getListener(), and that way be able to change the object that gets notified. Unfortunately, we’d still be unable to associate only one object per button, so let’s say we make it a list of “listeners” instead:

// Button action event
public class MyButton extends java.awt.Button
{   private Vector listeningObjects = new Vector();
    // Override action() to notify my new class
    public boolean action(Event evt, Object object)
    {      for (int i = 0; i < targetList.length; i++)
          ((ActionListener)(listeningObjects.elementAt(i))).action(evt, object);
    }   public void addActionListener(ActionListener newListener);
    {
       listeningObjects.addElement(newListener);
    }
    public void removeActionListener(ActionListener newListener);
    {
       listeningObjects.removeElement(newListener);
    }
}

Now, any instance of ActionListener can “listen” for events on any instance of MyButton by calling addActionListener(this), and can stop listening by calling removeActionListener(this). That’s all fine, but we’re still stuck with the inheritance problem: Only Buttons and ActionListener objects (and their descendents) can receive button action events. Java has a novel solution for this problem: the interface.

Interfaces and event listeners

The Java glossary provides the following definition:

interface: In Java, a group of methods that can be implemented by several classes, regardless of where the classes are in the class hierarchy.

Why would such a beast be useful?

An interface defines a “role” that any class may choose to play by implementing a set of operations that the interface defines.

An interface definition looks like a class definition:

// Still in JDK1.0
public interface ActionListener
{
    void action(Event evt, Object object);
}

Any class that wants to be a ActionListener simply has to define an action function, and then declare that it implements the ActionListener interface by using the implements keyword:

public class SomeRandomClass extends SomeOtherClass implements ActionListener
{   // Implement ActionListener methods
    void action(Event evt, Object object)
    {
        // Do whatever
    }
    SomeRandomClass()
    {
        super();
        // Blah blah blah...
    }
    // ... Continue with implementations of SomeRandomClass methods
}

This is extremely cool, because with interfaces, you’re no longer locked into a strict single-inheritance hierarchy.

In object-oriented parlance, inheritance often is called an ISA relationship: The class Person inherits Mammal because a Person ISA (is a) Mammal. Association of objects, wherein objects maintain references or pointers to one another, is sometimes called a HASA relationship: an Automobile HASA Crankshaft.

The interface construct adds to object-oriented thinking the concept of ACTS-AS-A. In this case, any class ACTSASA ActionListener simply by implementing the interface.

Every method in an interface is by definition abstract, meaning no implementation exists for the method (nor can it). So, if your class extends an interface, it must provide some implementation for every method defined in the interface.

The JDK 1.1 AWT defines interfaces for processing events, and the AWT user-interface elements provide the addEventtypeListener and removeEventTypeListener shown above. java.awt.Button in 1.1, for instance, has the methods:

void addActionListener(ActionListener listener)
void removeActionListener(ActionListener listener)

This means any ActionListener may add itself to the list of objects listening for Action events. A class qualifies as an ActionListener by implementing the ActionListener interface:

public interface ActionListener extends EventListener {
      public void actionPerformed(ActionEvent e)
}

Classes that want to receive events no longer have to inherit ActionListener — they can just implement it and play the role of ActionListener to some other object.

The single argument of the actionPerformed method, ActionEvent, is a class derived from java.util.EventObject. It provides several useful functions that help the event listener figure out who sent the event, what the state of the SHIFT and ALT keys were at the time the event occurred, and so on. Check out the full capabilities of these events in the online documentation (see Resources section below).

Also note that the interface extends EventListener, which itself is an interface with no methods! Requiring event listeners to extend the empty java.util.EventListener interface lets programs (especially IDEs) manipulate EventListeners of various subtypes in an abstract way (maintaining lists of EventListeners, for example).

What’s all this got to do with Beans?

We’ve just delved into the details of EventListener interfaces. This is supposed to be a column about JavaBeans: why all the AWT stuff?

JavaBeans communicate primarily using event listener interfaces.

Event listeners provide a general way for objects to communicate without being related by inheritance. As such, they’re an excellent communication mechanism for a component technology — namely, JavaBeans. While event listeners are found throughout the AWT, they’re not limited to user interfaces. They can be used to handle all sorts of events: property changes, sensor readings, timer events, filesystem activity, you name it.

Now comes the “Beany” part:

  1. You can define your own event types and event listener interfaces for them.

  2. If your new event type were called Eventtype, then your Beans can be sources of your new event type by implementing methods called:
    • addEventtypeListener(EventObject e)

    • removeEventtypeListener(EventObject e)
  3. Then, other Beans can be event targets by implementing the interface: EventtypeListener.

  4. And finally, you “wire” event sources and targets together by calling sourceBean.addEventtypeListener(targetBean).

Creating and using your own EventObject types

Let’s look at an example of creating an EventObject type by doing some brain surgery on one of last month’s examples, BarChartBean. I started by adding code to the BarChartBean that sets the percent property any time the user clicks or drags the mouse in the bar area. This gives us a way of using the mouse to produce a stream of changes to the percent property. (It also looks kind of groovy.)

The BarChartBean notifies other objects of changes to its percent property by using the PropertyChangeListener interface, a predefined, general form of event listener interface defined in the package java.beans. Let’s add another way for external Beans to be notified of a change to percent by defining a new event type, PercentEvent:

import java.util.*;
//
// This class encapsulates a change in percentage to pass
// to a "PercentListener".
//
public class PercentEvent extends java.util.EventObject
{
    protected int iOld_, iNew_;
    public PercentEvent(Object source, int iOld, int iNew)
    {
        super(source); iOld_ = iOld; iNew_ = iNew;
    }
    public int getOldPercent() { return iOld_; }
    public int getPercent() { return iNew_; }
    public int getChangedBy() { return iNew_ - iOld_; }
}

Remember before, when we talked about encapsulating class-specific data in events? Well, here it is: the old and new values of the percentage are specific to the PercentEvent event class.

Now, let’s define a listener interface for this new event type:

import java.util.*;
// Anyone who wants to listen for a change to "percent"
// should implement this interface
public interface PercentListener extends EventListener
{
    public void percentChanged(EventObject e);
}

Next, let’s make the BarChartBean into a PercentEvent source by implementing addPercentListener() and removePercentListener(), and by updating all of the listeners whenever the percent property changes. (We’ll just look at the relevant parts of the source code here.)

//
// BarChart Bean now accepts input
//
public class BarChartBean extends Canvas
    implements Serializable, PropertyChangeListener
{
// ...
   // List of percent listeners.
    private Vector percentListeners_;
// ... a whole lotta methods...
    // Set/Get methods for percent
    public void setPercent(int iPercent)
    {
        // Set new percent, and only if necessary repaint()
        // This is the only place that iPercent's range is controlled
    if (iPercent <= 100 && iPercent >= 0)
    {
            // Save old value, set new value FIRST
            int prevPercent = iPercent_;
        iPercent_ = iPercent;
            // Notify property listeners of change to "percent" property
            pcs_.firePropertyChange("percent",
                                    new Integer(prevPercent),
                                    new Integer(iPercent_));
            // Notify all listeners for "percent" change
            notifyPercentChanged(prevPercent, iPercent);
            // Repaint only if necessary.
            if (prevPercent != iPercent_)
            {
                repaint();
            }
    }
    }
// ...
    //
    // These methods are for handling "PercentListeners"
    //
    // Add a new percent listener
    public synchronized void addPercentListener(PercentListener listener)
    {
        percentListeners_.addElement(listener);
    }
    // Remove a percent listener
    public synchronized void removePercentListener(PercentListener listener)
    {
        percentListeners_.removeElement(listener);
    }
    // Notify all listeners that "percent" changed
    protected void notifyPercentChanged(int oldPct, int newPct)
    {
        Vector thisList = new Vector();
        PercentEvent thisEvent = new PercentEvent(this, oldPct, newPct);
        // Make a copy of the list so potential changes to it by
        // other threads won't affect traversal.
        synchronized (this) {
            thisList = (Vector)percentListeners_.clone();
        }
        // Send a "PercentEvent" to every listener.
        for (int elem = 0; elem < thisList.size(); elem++) {
            ((PercentListener)thisList.elementAt(elem)).percentChanged(thisEvent);
        }
    }

The vector percentListeners_ is a list of PercentListeners (objects that implement the PercentListener interface) that want to be notified whenever the percent property changes. Further down in the source, the setPercent() method calls firePropertyChange() as before, but now it also calls notifyPercentChanged() to notify all objects in the list percentListeners_. (We are actually providing here two ways to be notified of percent changes: as a PropertyChange, and now as a PercentEvent.)

The addPercentListener() and removePercentListener() methods simply add and remove objects from the listener list. They are synchronized because multiple threads might be trying to add or remove listeners to or from the list. If a context switch were to occur in the middle of processing this list, Horrible Things Could Happen. (The list could be corrupted, resulting in (if you’re lucky) hard-to-find errors, and (if you’re not) spooky application behavior or even data corruption.)

The real work is done in notifyPercentChanged(). A new PercentEvent is built from the data passed in, and then the percentListeners_ list is cloned, meaning it’s completely copied to a new vector. The synchronized keyword supplies a clue to why we do this: What would happen if another thread were to remove a listener while we were partially through traversing the list? (See “Horrible Things” in the previous paragraph.) The loop simply passes the PercentEvent to all of the listeners on the copy of the list we’ve made.

Of course, an event source is of no use to us without an event target. Let’s create a little Bean that’s just a label that changes when it receives a PercentEvent:

import java.util.*;
import java.io.*;
import java.awt.*;
import PercentListener;
import PercentEvent;
public class PercentLabel 
    extends Label
    implements Serializable, PercentListener
{
    public PercentLabel() { }
    public void percentChanged(EventObject event)
    {
        if (event instanceof PercentEvent)
        {
            PercentEvent pe = (PercentEvent) event;
            setText(Integer.toString(pe.getPercent()));
        }
    }
}

Simple enough. Now, all that remains is to connect the two together:

import java.awt.*;
import java.io.*;
import BarChartBean;
public class Example
    extends Panel
    implements Serializable
{
    private PercentLabel pl_ = new PercentLabel();
    private BarChartBean bcb_ = new BarChartBean();
    public Example()
    {
        bcb_.addPercentListener(pl_);
        setLayout(new BorderLayout());
        add("North", pl_);
        add("South", bcb_);
    }
}

And here is the resulting Bean:

You can download the JAR file and try this for yourself in the BeanBox.

Now, since PropertyChange interfaces exist, why would anyone want to go to the extra work of creating their own event types? One reason might be that you want to pass more information in the event than a PropertyChange will allow. You can encapsulate anything you want in an event. The PropertyChange mechanism we saw last month is actually implemented in terms of the event listeners we’ve just seen.

Events are also more general than PropertyChanges. The new AWT, for example, defines event types for mouse motions, key presses, component resizing, and more. You may want to define a new event class for a new kind of occurrence in your application, and not all of these are most intuitively modeled as PropertyChanges. For example, in a ModemControl Bean, you could create a new ModemEvent class, create an appropriate listener interface, and let other Beans listen for, say, ModemConnectEvent, ModemDisconnectEvent, and so on. This makes more sense than having a property called “ModemState” (or something), that’s read-only in some cases and not in others, and really isn’t a “property” anyway.

IDEs can use the new Java reflection mechanism, which lets Java programs analyze class files, to analyze Beans. IDEs look for classes that implement EventListener (to find event targets), and also for methods that have names that look like addSomethingListener() (to find event sources). (Remember above the comment about the empty EventListener interface being useful to IDEs — this is why.) You also can add methods to your Beans to explicitly tell IDEs (or whoever else asks) what events your Bean produces or processes, but that’s for a later column.

We just hand-coded a class called Example that “wired” one Bean to another using a listener interface. Let’s do it one better and do the same thing visually. Follow these steps with the BeanBox:

  1. Open the BeanBox with ColorBar.jar in your jars directory, so the example Beans are loaded.

  2. Add a BarChartBean to the BeanBox.

  3. Add a PercentLabel to the BeanBox (it will be white so it’s easier to see).

  4. Select the BarChartBean that’s in the BeanBox.

  5. Select the menu item Edit->Events->percent->percentChanged. Now check it out: The BeanBox automatically identified a new event type and added it to the list of events you can deliver from the BarChartBean. Cool!

  6. You’ll get a red rubber-banding line. Slide the mouse over to the PercentLabel and click it.

  7. A dialog box will come up asking you which method you want to call when a percentChanged event comes in to the PercentLabel. Choose percentChanged.

    The BeanBox creates an “adaptor” class, which is a PercentListener. The BeanBox then compiles the adaptor class it wrote, creates an instance of it, and calls BarChartBean.addPercentListener(theAdaptor) on it. When the BarChartBean generates percent events, it passes them to the adaptor (because the adaptor’s “listening”), which then passes the event to PercentListener.percentChanged().

    In fact, here’s the code for the “adaptor” class the BeanBox generated:

    // Automatically generated event hookup file.
    package tmp.sun.beanbox;
    import PercentLabel;
    import PercentListener;
    public class ___Hookup_1452f9e502 implements PercentListener, java.io.Serializable {
        public void setTarget(PercentLabel t) {
            target = t;
        }
        public void percentChanged(java.util.EventObject arg0) {
            target.percentChanged(arg0);
        }
        private PercentLabel target;
    }
    
  8. Now, simply use the BarChartBean as before, and the PercentListener automatically tracks the percent. Visual application building!

If your browser supports animated GIF files, click here to see an animation demonstrating how this works.

Bean-a-palooza: Grouping events of the same type

When you’re writing listener interfaces, keep in mind that it’s often easier to create a single listener interface containing an Eventtypeperformed() method for several related events, instead of creating one listener interface for each.

A good example of this concept appears in the way mouse events are handled in the JDK 1.1 AWT. (In fact, there are two such interfaces, grouping mouse events differently for efficiency.)

To catch mouse events in the new AWT, a class implements one or both of two interfaces: java.awt.event.MouseListener or java.awt.event.MouseMotionListener. These interfaces contain the following methods:

public interface MouseListener extends EventListener
{
    public void mouseClicked(MouseEvent e);
    public void mouseEntered(MouseEvent e);
    public void mouseExited(MouseEvent e);
    public void mousePressed(MouseEvent e);
    public void mouseReleased(MouseEvent e);
};
public interface MouseMotionListener extends EventListener
{
    public void mouseDragged(MouseEvent e);
    public void mouseMoved(MouseEvent e);
}

Now, the designers of the AWT could have defined a large number of interfaces and associated event types:

public interface MouseClickListener extends EventListener
{
    public void mouseClicked(MouseClickEvent e);
}
public interface MouseEnterListener extends EventListener
{
    public void mouseEntered(MouseEnterEvent e);
}
// and so on ad nauseum

Instead, the AWT designers grouped several methods into each of these two interfaces. A class that wants to receive mouse events simply needs to declare that it implements MouseListener, and then implement all of the methods void mouseClicked(MouseEvent e), void mouseEntered(MouseEvent e), and so on, performing whatever actions it pleases when the event arrives. If you’re not interested in, say, mouseExits, then you simply implement a method that does nothing. (Java requires that all methods be defined to implement an interface. Your class won’t compile if they’re not. There is a way around this though — see “Do Nothing Classes” below.)

Well, you ask (you did ask, didn’t you?), Why didn’t the Java designers group all mouse event handlers into the same interface, instead of splitting them into MouseListener and MouseMotionListener? This was a performance decision: Mouse motion events come thick and fast, much faster than mouse button clicks (unless you’ve been hitting the espresso a bit too often). If no class is listening for mouse motion events, the AWT (internally) simply discards any such events it receives, saving the time it would have spent calling a method that was just going to do nothing anyway.

Earlier I mentioned a way to get around Java’s annoying insistence that all methods of an interface be defined, even if they do nothing. Let’s say we’re writing a class that simply keeps track of how many times the mouse button has been clicked. The relevant section of this class would look something like this:

// count right mouse clicks
public class ClickCounter extends Panel implements MouseListener
{
    private int iClicks_ = 0;
    // Listen for events on self
    ClickCounter() { addMouseListener(this); }
    public void mouseClicked(MouseEvent e) { iClicks_++; repaint(); }
    public void mouseEntered(MouseEvent e) { }
    public void mouseExited(MouseEvent e) { }
    public void mousePressed(MouseEvent e) { }
    public void mouseReleased(MouseEvent e) { }
    public void paint() { // paint the # of clicks on the panel }
}

It’s irritating to have to declare all of these worthless empty functions. Fortunately, the designers of the AWT solved that problem for you: They created a class that does absolutely nothing.

Do-nothing classes

The class java.awt.event.MouseAdapter does precisely nothing. It implements the MouseListener class with methods that ignore the events they receive. Of what possible use is a class whose methods do nothing? By itself, very little — but you can subclass MouseAdapter and implement only those methods that interest you. The ClickCounter example above could then be implemented like this:

public class ClickCounter extends MouseAdapter
{
    private int iClicks_ = 0;
    // Listen for events on self
    ClickCounter() { addMouseListener(this); }
    public void mouseClicked(MouseEvent e) { iClicks_++; repaint();}
    public void paint() { // paint the # of clicks on the panel }
}

Instead if defining the class as implements MouseListener, you say that it extends MouseAdapter, and then the “stub” functions in the MouseAdapter class take care of doing nothing (so you don’t have to write methods that do nothing). All you have to write is mouseClicked(), which is all you were interested in, anyway. Convenient, huh?

You’ll find that most of the groups of event listener interfaces in the AWT consist of three related definitions:

  • EventtypeEvent: the event that occurs
  • EventtypeListener: the interface that listens
  • EventtypeAdapter: an adapter class that does “nothing” so you don’t have to

The important implications of all this for Beans are:

When creating event listener interfaces for your Beans:

  1. Group related event subtypes in the event listener interface.
  2. If there’s more than one method in the interface, provide an adapter class to make your interface easier to use.

Inner classes

Imagine you have 20 user interface widget types, and they all do something specific to themselves when a MouseEvent occurs. Your namespace can get cluttered with all sorts of event handler classes: DrawPaneMouseEvent, MousePositionMouseEvent, and so on. And these classes are only applicable to the classes that “own” them. Java 1.1 has a new feature called inner classes that allows you to control the scoping of class definitions.

An inner class in Java is a class that is defined within another class. Its “scope” (or “visibility”) is limited to within the block where the class is defined.

Inner classes are a new feature of Java 1.1, and are useful for (among other things) defining adapter classes that “know” about the implementation details of the object they’re manipulating. The ClickCounter provides an opportunity for a (very simple) example.

public class ClickCounter extends Panel
{
    private int iClicks_ = 0;
    class MouseHandler extends MouseAdapter
    {
        public void mouseClicked(MouseEvent e) { iClicks_++; repaint();}
    }
    // Listen for events on self
    ClickCounter() { addMouseListener(new MouseHandler()); }
    public void paint() { // paint the # of clicks on the panel }
};// <blink>This is illegal and would not compile!</blink>
MouseHandler m = new MouseHandler()

This defers processing of the mouse-click events to an inner class called MouseHandler, which knows about the _iClicks variable of its containing class! If you were to try to do this with a “top-level” class (that is, any class that’s not an inner class), you couldn’t access the implementation details (iClicks_) of the ClickCounter. You’d have to add some methods to manipulate the count, thereby breaking encapsulation. (The phrase “top-level class” is a retronym, meaning a new name for something old that makes a distinction that didn’t previously exist. “Analog watch” is a retronym.)

I deliberately included an error in this example: the definition of a MouseHandler object outside of the scope of the ClickCounter class. This won’t work because the MouseHandler is not defined outside of the ClickCounter class. If some other top-level class happened to be called MouseHandler, then the code would compile, but the MouseHandler m would not be of the same type as ClickCount’s inner class. That’s as it should be: Separating namespaces in this way is one of the purposes of inner classes.

There’s one important thing to note about inner classes: Although their scoping is different from that of top-level classes, each inner class you define compiles to its own class file. On Win32, the filename for the ClickCounter class would be ClickCounter$MouseHandler.class. (Check the documentation or do a little experiment to find out how it works on your system.) The important point to remember here is that, even though these inner classes aren’t Beans, any inner classes your Beans define will need their (inner) class files, or the Bean simply won’t operate. So, be sure to include in your JAR file the class files for any inner classes you define.

Check out the source for the BarChartBean if you want to see another example of handling the mouse using inner class event adaptors. For this source and for more on inner classes and what you can do with them (some of it’s pretty weird), see Resources below.

Conclusion

We’ve been over a lot this month. First, we discussed what events are and what they’re for. We examined some of the problems with the old event structure of Java 1.0, and then explained how the new structure works in Java 1.1. We learned about interfaces as a prelude to discussing one type of interface, the event listener. Then, we went over how event listeners can be used to wire JavaBeans together, and went through a coded example that did just that. We even took a look at an example of connecting Beans together with no coding whatsoever. We covered some general rules for creating your own event-listener interfaces, and learned about event adaptors and inner classes in the process.

By popular request, next month we’ll be returning to the topic of JavaBean customization, and learn to write custom property editors, BeanInfo objects, and Bean customizers.

Keep the Java hot!

Mark Johnson has a BS in Computer and Electrical Engineering from Purdue University (1986). He is a fanatical devotee of the Design Pattern approach in object-oriented architecture, of software components in theory, and of JavaBeans in practice. Over the past several years, he worked for Kodak, Booz-Allen and Hamilton, and EDS in Mexico City, developing Oracle and Informix database applications for the Mexican Federal Electoral Institute and for Mexican Customs. He currently works as a designer and developer for Object Products in Fort Collins, CO.