Monday, July 2, 2012

Java Event Handling, Revisited

  Quite a while ago I blogged about replacement code for Java's javax.swing.event.EventListenerList, which never appealed to me.  (Blog posts are Here, here, and here)  The EventListenerList code, is, well, icky.  You jump by 2 along an array of mixed Classes and Listeners, and non-reassuring comments in the source code like "it provides ... a degree of MT (multi-threaded) safety (when used correctly)."  It was written before the newfangled java.util.concurrent code, such as the CopyOnWrite collections, which were written by experts and are perfect for this task. 

  Also, to use EventListenerList, you are required to define a little Listener class for each type of event to be fired, which is repetitive boilerplate work.  Now that Java has Generics this is less necessary.  EventListenerList will only handle events that implement the marker interface EventListener.  Which isn't horrible, but, if you want a more general purpose class to "fire" something else, say a String, to listeners, it won't do the job.  Finally,  EventListenerList depends on Swing. If your project targets Android, you don't have Swing.  So, if you like (or are just plain used to) the concept of a decoupled event listener, but are looking for something a bit easier to use and with broader application, this code might be for you.

  I have slightly updated and reorganized the code, which is available on Assembla.  The source code is here, and the JUnit tests here.

  Events.java defines three inner interfaces.  In the future, I may add some utility methods here to manipulate events.

  1. Events.Listener defines a listener, generically, based upon the type of event it listens for.  No need to create an actually boilerplate class for each different listener.
  2. Events.Broadcaster is a convenience interface for any class that fires a single type of event.  You don't need to define and implement it (Java never defined such as interface) but it could be useful.
  3. Events.Broadcasters is a convenience interface for any class that fires multiple types of events.
Broadcaster is an implementation of Events.Broadcaster to fire a single type of event.  In the common case where there are 0 or 1 listeners, it just uses a direct reference, aListener.  When there are multiple listeners, it uses a CopyOnWriteArraySet.  Note that this avoids the common practice of adding the same listener twice, which, in my experience, is almost always a bug.

Broadcasters is an implementation of Events.Broadcasters.  It can fire multiple types of event, using a HashMap to redirect them by the specific class of the event.  Note that it uses == logic, not instanceof.  If you want to subclass an event (in my experience, extremely rare - look at the built in Java events) you'll have to do something clever.


  One drawback of this concept is that, due to erasure, a class cannot listen to two types of events.  That is, you cannot go

public class FooBarListener implements Events.Listener, Events.Listener 

One option is to implement Double Dispatch, as illustrated in DoubleDispatchUnitTest.  I find double-dispatch confusing, so the simpler alternative is to define your separate listeners in inner classes, e.g.

public class FooBarListener {

   private Events.Listener fooListener = new Events.Listener() {
       public void handleEvent(Foo fooEvent) {
         // do stuff here
      }
   };

   private Events.Listener barListener = new Events.Listener() {
       public void handleEvent(Bar barEvent) {
         // do stuff here
      }
   };

}





No comments:

Post a Comment