Re: Generics
On Jun 27, 8:09 am, kofa <kovacs...@gmail.com> wrote:
Hi,
I have a problem that I have been unable to code cleanly with
generics. I suspect it is not possible because of no run-time generics
info. The problematic code (a cast, highlighted in capitals in
comments) is in dispatchEvent(Event e) below.
Suppose I have a hierarchy of events. XEvent extends Event, YEvent
extends Event etc.
The purpose is to provide a type-safe event listener and an event
dispatcher.
I could create the interface:
public interface <T extends Event> EventListener {
void eventRaised(T event);
}
And also:
public interface EventManager {
<T extends Event> void addListener(EventManager<T> listener,
Class<T> type);
<T extends Event> void removeListener(EventManager<T> listener,
Class<T> type);
void raiseEvent(Event event);
}
I think the fact that I need to specify T in addListener twice (in
listener type and class type) already shows something's wrong...
Now implementing this seems impossible without casts and unchecked
types:
Truth 6a: It is always possible to add another level of indirection.
I've written generic event dispatchers for my own code, and I'm happy
to donate one as an example. The trick to this problem is to add
another class (which I called a "firer") which encapsulates the
knowledge of how to process each listener.
$ cat EventDispatcher.java
package xxxxx;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
/**
* Event dispatcher and listener management class. Event listeners can
be
* registered with this class, and then subsequently fired. When an
event is
* fired by a dispatcher, all registered listeners are notified in
reverse order
* of registration.
*
* @param <L>
* the event listener type.
*/
public class EventDispatcher<L> {
private final List<L> listeners = new ArrayList<L> ();
/**
* Fire events on this dispatcher using a given event firer.
*
* @param firer
* the event firing mechanism to use.
*/
public void fire (final EventFirer<? super L> firer) {
if (firer == null) {
throw new IllegalArgumentException ("firer");
}
final ListIterator<L> iter = getIteratorAtEnd ();
while (iter.hasPrevious ()) {
firer.fireEvent (iter.previous ());
}
}
/**
* Adds an event listener to the dispatch sequence. Events will be
dispatched
* to the most recently added listener first. The same listener may
be added
* more than once, and will be notified once for each time it's
registered.
*
* @param listener
* the event listener to register.
* @see #removeListener(Object listener)
*/
public void addListener (final L listener) {
if (listener == null) {
throw new IllegalArgumentException ("listener");
}
listeners.add (listener);
}
/**
* Removes an event listener from the dispatch sequence. If the
listener was
* registered more than once, only the most recent occurrence is
removed.
*
* @param listener
* the event listener to remove.
* @see #addListener (Object listener)
*/
public void removeListener (final L listener) {
final ListIterator<L> listenersIterator = getIteratorAtEnd ();
while (listenersIterator.hasPrevious ()) {
final L registered = listenersIterator.previous ();
if (registered == listener) {
listenersIterator.remove ();
return;
}
}
}
private ListIterator<L> getIteratorAtEnd () {
return listeners.listIterator (listeners.size ());
}
}
// --- END ---
$ cat EventFirer.java
package xxxxx;
/**
* Interface for actually firing a single event, used by {@link
EventDispatcher}s.
*
* @param <L>
* the listener type to fire events on.
*/
public interface EventFirer<L> {
/**
* Fire the event on a single listener.
*
* @param listener
* the listener to fire.
*/
public void fireEvent (L listener);
}
// --- END ---
At this point the "Listener" interface can be any event listener with
no particular requirements for descending from any specific interface
or class, which is nice. The code that uses these classes looks like
/**
* Calls the {@link Listener#connected()} method on all attached
listeners.
*/
protected void fireConnected () {
connectorListeners.fire (new EventFirer<Listener> () {
public void fireEvent (Listener listener) {
listener.connected ();
}
});
}
This does tend to litter code with anonymous classes, but they're not
huge -- most of them are one line. As an added bonus the event
listener interfaces can have any combination of arguments; the
dispatcher doesn't worry about argument passing itself and firers are
easy to implement.
Owen