views:

311

answers:

4

Related: http://stackoverflow.com/questions/1391918/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?

A: 

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

Rob H
Two problems with that: 1. garbage-collected references will remain in the set forever (WeakHashMap automatically expunges them) and 2. `WeakReference` does not override `equals()`, so you can end up with multiple references to the same listener in the set but you cannot tell that they are duplicates.
finnw
True. Perhaps you could overcome this, though, by extending CopyOnWriteArraySet to examine the referent rather than the WeakReference when the collection mutates and manually expunge obsolete references. I don't believe anything in the standard Collections offers everything you want out-of-the-box.
Rob H
A: 

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

Damien B
+3  A: 

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.

kdgregory
+4  A: 

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.

Oleg Ryaboy
Yes this is the sort of thing I had in mind. +1
finnw