patternjavaMinor
Using a List to manage Swing event listeners
Viewed 0 times
manageswingusinglistenerslistevent
Problem
I've been playing with an idea for simplifying management of event listeners on custom Swing components.
Swing provides the EventListenerList class for maintaining events, and every JComponent has a
The thing is, I think EventListenerList is poorly designed. They tried to minimize memory usage by packing all of a component's listener types together. But it doesn't save much memory, and it reallocates the array on every add/remove, and it has a bothersome bunch of boilerplate code needed. For each event type, components using it have the following methods (using ActionListener and a hypothetical button component as an example):
```
/**
* Adds an {@code ActionListener} to the button.
*
* @param l the listener to be added
*/
public void addActionListener(ActionListener l) {
listenerList.add(ActionListener.class, l);
}
/**
* Removes an {@code ActionListener} from the button.
*
* @param l the listener to be removed
*/
public void removeActionListener(ActionListener l) {
listenerList.remove(ActionListener.class, l);
}
/**
* Returns an array of all the {@code ActionListener}s added
* to this button with {@link #addActionListener}.
*
* @return all of the {@code ActionListener}s added or an empty
* array if no listeners have been added
*/
public ActionListener[] getActionListeners() {
return listenerList.getListeners(ActionListener.class);
}
/**
* Notifies all listeners that have registered interest for
* notification on this event type.
*
* @param event the {@code ActionEvent} object
*/
protected void fireActionPerformed(ActionEvent event) {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length-2; i>=0; i-=2) {
if (listeners[i]==ActionL
Swing provides the EventListenerList class for maintaining events, and every JComponent has a
protected EventListenerList listenerList = new EventListenerList(); available for use by subclasses.The thing is, I think EventListenerList is poorly designed. They tried to minimize memory usage by packing all of a component's listener types together. But it doesn't save much memory, and it reallocates the array on every add/remove, and it has a bothersome bunch of boilerplate code needed. For each event type, components using it have the following methods (using ActionListener and a hypothetical button component as an example):
```
/**
* Adds an {@code ActionListener} to the button.
*
* @param l the listener to be added
*/
public void addActionListener(ActionListener l) {
listenerList.add(ActionListener.class, l);
}
/**
* Removes an {@code ActionListener} from the button.
*
* @param l the listener to be removed
*/
public void removeActionListener(ActionListener l) {
listenerList.remove(ActionListener.class, l);
}
/**
* Returns an array of all the {@code ActionListener}s added
* to this button with {@link #addActionListener}.
*
* @return all of the {@code ActionListener}s added or an empty
* array if no listeners have been added
*/
public ActionListener[] getActionListeners() {
return listenerList.getListeners(ActionListener.class);
}
/**
* Notifies all listeners that have registered interest for
* notification on this event type.
*
* @param event the {@code ActionEvent} object
*/
protected void fireActionPerformed(ActionEvent event) {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length-2; i>=0; i-=2) {
if (listeners[i]==ActionL
Solution
Certainly it solves my main complaint which is the amount of code.
But it raises another issue, namely the amount of Lists floating around. In the class
Even if using nullable references, it'd be 80 more bytes per component. With real lists, you add 3 objects ("checked", "List", and its array).
For your own listener type or two, it may be OK, for AWT/Swing it would be too bad.
I can't prevent null being accidentally added to the list
What's the problem? Make an own null-forbidding list or check when iterating.
When there are multiple listeners the order of event dispatch is reversed compared to the normal pattern.
The normal pattern is not guaranteed. It mostly works, but not always. You can't even rely on Swing handlers firing before yours. You could use
With your own Listener, you could rely on the order, but probably you should not, as in a complex program, listeners may get added and removed and added again and whatever...
It's not safe for manipulation during iteration.
Ditch the iterator and use an index. Or write an own List.
Anything else?
The memory problem may be a problem of yours, too.
I personally would reduce the Javadoc to something like
if any. Or, if you need it many times, implement an interface and document it there (no need to re-document anything in the class as it adds absolutely nothing new).
A simple helper would reduce
I actually consider the idea of
would be longer and probably slower, but IMHO much nicer. But it's a different story and it comes 20 years too late.
"Ditch the iterator and use an index." While that would prevent throwing the CME, that just hides the problem.
No, you can do it in a consistent way, so that after the removal, all remaining listeners get iterated.
And it complicates the boilerplate, which I specifically want to avoid unless it does something really worthwhile.
You can extract the all the boilerplate into a single helper method and then maybe 10 lines are no problem.
(4) "Or write an own List." I don't know how to make that safe for modification during iteration either though.
The simplest solution would be a
Looking at the sources of
(5) "A simple helper would reduce fireActionPerformed to a single line" Yes but ActionListener is only one interface; I can't make a generic helper for all listener interfaces.
I see that the
and reduce it to a simple loop. If you need
You could use reflection instead and write a universal method accepting the method name, but that's ugly.
in which case it's better not to do anything too weird.
I'm afraid so.
The problems are that the listener have no common class, are not parameterized with the event type, and have multiple differently named methods instead of overriding a single one. I could imagine something like
be the best for Java 8, but it's not exactly nice and it's too late.
But it raises another issue, namely the amount of Lists floating around. In the class
Component, there are 11 different event types and I don't want to know how many others exist elsewhere. So you'd have to add 20 lists to every component. Note that most components have no listeners.Even if using nullable references, it'd be 80 more bytes per component. With real lists, you add 3 objects ("checked", "List", and its array).
For your own listener type or two, it may be OK, for AWT/Swing it would be too bad.
I can't prevent null being accidentally added to the list
What's the problem? Make an own null-forbidding list or check when iterating.
When there are multiple listeners the order of event dispatch is reversed compared to the normal pattern.
The normal pattern is not guaranteed. It mostly works, but not always. You can't even rely on Swing handlers firing before yours. You could use
ListIterator.prev(), but I wouldn't care (I might even try to randomize the order instead, so I get a chance to catch depending on order early).With your own Listener, you could rely on the order, but probably you should not, as in a complex program, listeners may get added and removed and added again and whatever...
It's not safe for manipulation during iteration.
Ditch the iterator and use an index. Or write an own List.
Anything else?
The memory problem may be a problem of yours, too.
I personally would reduce the Javadoc to something like
/** @see Button.addActionListener. */if any. Or, if you need it many times, implement an interface and document it there (no need to re-document anything in the class as it adds absolutely nothing new).
A simple helper would reduce
fireActionPerformed to a single line, too.I actually consider the idea of
add/remove/get/fire rather strange. Looking at any AWT/Swing class reveals tons of such methods with no chance to quickly find what you want. Something likesomething.listeners(ActionListener.class).add(myFancyActionListener);would be longer and probably slower, but IMHO much nicer. But it's a different story and it comes 20 years too late.
"Ditch the iterator and use an index." While that would prevent throwing the CME, that just hides the problem.
No, you can do it in a consistent way, so that after the removal, all remaining listeners get iterated.
And it complicates the boilerplate, which I specifically want to avoid unless it does something really worthwhile.
You can extract the all the boilerplate into a single helper method and then maybe 10 lines are no problem.
(4) "Or write an own List." I don't know how to make that safe for modification during iteration either though.
The simplest solution would be a
CopyOnWriteArrayList, which guarantees to iterate a consistent snapshot and never throw a CME.Looking at the sources of
ArrayList and just dropping the co-modification test should do, too, but then I'd suggest to implement only the needed methods rather than the whole List interface.(5) "A simple helper would reduce fireActionPerformed to a single line" Yes but ActionListener is only one interface; I can't make a generic helper for all listener interfaces.
I see that the
fireXXX events are a problem, but you could use List getListeners(Class listenerClass, Object[] listeners);and reduce it to a simple loop. If you need
fireMouseMoved often, you could writevoid helpFireMouseMoved(MouseEvent e, Object[] listeners) {
for (MouseEventListener l : getListeners(MouseMotionListener.class, listeners) {
l.mouseMoved(e);
}
}You could use reflection instead and write a universal method accepting the method name, but that's ugly.
in which case it's better not to do anything too weird.
I'm afraid so.
The problems are that the listener have no common class, are not parameterized with the event type, and have multiple differently named methods instead of overriding a single one. I could imagine something like
interface MouseMovedListener extends EventListener {
@Override react(e);
}be the best for Java 8, but it's not exactly nice and it's too late.
Code Snippets
/** @see Button.addActionListener. */something.listeners(ActionListener.class).add(myFancyActionListener);<L extends EventListener> List<L> getListeners(Class<L> listenerClass, Object[] listeners);void helpFireMouseMoved(MouseEvent e, Object[] listeners) {
for (MouseEventListener l : getListeners(MouseMotionListener.class, listeners) {
l.mouseMoved(e);
}
}interface MouseMovedListener extends EventListener<MouseMovedEvent e> {
@Override react(e);
}Context
StackExchange Code Review Q#71158, answer score: 3
Revisions (0)
No revisions yet.