I'm trying to create a lightweight, thread-safe in-app publish/subscribe mechanism for an Android app that I'm building. My basic approach is to keep track of a list of IEventSubscriber<T>
for each event type T and then be able to publish events to subscribing objects by passing along a payload of type T.
I use generic method parameters to (I think) ensure that subscriptions are created in a type safe way. Thus, I'm pretty sure that when I obtain the list of subscribers from my subscription map when it comes time to publish an event that I'm OK casting it to a list of IEventSubscriber<T>
, however, this generates the unchecked cast warning.
My questions:
- Is the unchecked cast actually safe here?
- How can I actually check to see if the items in the subscriber list implement
IEventSubscriber<T>
? - Presuming that (2) involves some nasty reflection, what would you do here?
Code (Java 1.6):
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;
public class EventManager {
private ConcurrentMap<Class, CopyOnWriteArraySet<IEventSubscriber>> subscriptions =
new ConcurrentHashMap<Class, CopyOnWriteArraySet<IEventSubscriber>>();
public <T> boolean subscribe(IEventSubscriber<T> subscriber,
Class<T> eventClass) {
CopyOnWriteArraySet<IEventSubscriber> existingSubscribers = subscriptions.
putIfAbsent(eventClass, new CopyOnWriteArraySet<IEventSubscriber>());
return existingSubscribers.add(subscriber);
}
public <T> boolean removeSubscription(IEventSubscriber<T> subscriber,
Class<T> eventClass) {
CopyOnWriteArraySet<IEventSubscriber> existingSubscribers =
subscriptions.get(eventClass);
return existingSubscribers == null || !existingSubscribers.remove(subscriber);
}
public <T> void publish(T message, Class<T> eventClass) {
@SuppressWarnings("unchecked")
CopyOnWriteArraySet<IEventSubscriber<T>> existingSubscribers =
(CopyOnWriteArraySet<IEventSubscriber<T>>) subscriptions.get(eventClass);
if (existingSubscribers != null) {
for (IEventSubscriber<T> subscriber: existingSubscribers) {
subscriber.trigger(message);
}
}
}
}