Question

Related: Does java have a "LinkedConcurrentHashMap" data structure?


I am looking for a collection class to hold references to event listeners.

Ideally I would like the collection to have the following properties (in order of priority):

  1. Maintains insertion order. The earlier listeners may cancel the event, preventing it from being delivered to listeners added later. This will break if using a class such as HashSet whose iterator may return elements in the wrong order.
  2. Uses WeakReferences so that the listener list does not prevent the listeners from being garbage-collected.
  3. The collection is a Set, so duplicates are automatically removed.
  4. The Iterator is a thread-safe snapshot of the collection, unaffected by the addition of new listeners. Also allows events to be delivered on multiple threads. (This is not essential - I could iterate over a clone of the set instead.)

I am aware of some classes that satisfy some but not all of these criteria. Examples:

  • java.util.LinkedHashSet (#1 and #3)
  • java.util.WeakHashMap, wrapped by Collections.newSetFromMap (#2 and #3)
  • javax.swing.event.EventListenerList (needs some extra synchronization) (#1 and #4)
  • java.util.concurrent.CopyOnWriteArraySet (#1, #3 and #4)

But nothing with both #1 and #2. Does class like this exist in a library somewhere?

Was it helpful?

Solution

You could use WeakListeners (see http://bits.netbeans.org/dev/javadoc/org-openide-util/org/openide/util/WeakListeners.html) and CopyOnWriteArraySet.

  1. Implement a remove(ListenerType listener) method in your event source.
  2. In your register(SomeListener listener) method, add a WeakListener to the collection instead:

    listenerCollection.put((ListenerType)WeakListeners.create ( ListenerType.class, listener, this));

When the real listener is removed from memory, the weak listener will be notified, and it will unregister itself. (This is why it needs the reference to the source (this) for the registration.) The unregistration is done using reflection by calling the method remove of the source.

OTHER TIPS

I'm going to start by saying that you have a couple of requirements that don't make sense together. You're looking for a collection that removes duplicates and supports weak references, which indicates to me that listeners may appear and disappear at indeterminate times. Yet you want to maintain insertion order, and allow one listener to cancel all subsequent notifications. To me, this sounds like a recipe for hard-to-find bugs, and I strongly suggest rethinking it.

That said, you have one requirement that pretty much drives the solution: you don't want the ConcurrentModificationException that could come from a normal iterator. Which means that you're going to have to copy the original list. Along the way, you can check and remove the empty references:

// the master list
List<WeakReference<MyListener>> _list = new ArrayList<WeakReference<MyListener>>();

// inside your send-notification method
List<MyListener> toNotify = new ArrayList<MyListener>(_list.size());
Iterator<WeakReference<MyListener>> itx = _list.iterator();
while (itx.hasNext())
{
    WeakReference<MyListener> ref = itx.next();
    MyListener lsnr = ref.get();
    if (lsnr != null)
        toNotify.add(lsnr);
    else
        itx.remove();
}

// now iterate "toNotify" and invoke the listeners

You're probably freaking out now, saying "a List! that's a linear data structure! I can't use that, insertion is O(N)!"

Well, yes you can. I don't know how many listeners you're planning to have. But as long as you're < 100 (and more likely < 100,000), the cost of a linear search for insert and remove isn't going to matter.

Far more interesting from a coding perspective is how you deal with the weak reference. You'll note that I explicitly dereference it, into a variable, before testing the referent for null. This is critically important code when dealing with reference objects: although it's extremely unlikely that the referent will be collected between two calls to get(), it's possible.

Which brings me to the WeakReference itself. You'll need to create your own subclass that overrides the equals() and hashCode() methods to delegate to its referent. I thought I had just such a class lying around, but apparently not, so will leave it for you to implement.

A Set is the proper collection to use with listeners.

If you rely on the insertion order of listeners your design is broken. It misses the point of listeners to be ISOLATED and INDEPENDENT from other listeners. Use Sets instead of Lists.

If you rely on WeakReferences your design is broken. Remove listeners in the same object where you added it. This SYMMETRY supports READABILITY and MAINTAINABILITY. To resolve programming errors of forgotton unsubcriptions of listeners with weak references only hides the problem.

If you provide your collection of listeners to other objects than to your observed object then your design is broken. Keep the Set private to support ENCAPSULATION.

If you override equals and hashcode of your listeners your design is broken. It hides the problem of unneccessary function calls. Prevent unneccessary calls instead. After all overiding equals and hashcode of listeners is not neccessary.

In MULTITHREADING environments put an MONITOR on the resource "listeners" while adding, removing or iterating over it. You may create a DEFENSIVE COPY before you iterate to avoid a ConcurrentModificationException. Then the iteration does not have to be SYNCHRONIZED, but the copy action should.

Any other requirement has to be adapted or reformulated to match these statements. Any other practise will lead to unmaintainable code, memory leaks because of lacking isolation, independency, encapsulation and clarity.

You could wrap each listener reference in a WeakReference and then use CopyOnWriteArraySet.

You could extend WeakReference to override equals and hashcode, then you can use them in a LinkedHashSet.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top