Not really. It will be safe if all clients of the EventManager class always use generics and never use rawtypes; that is, if the client code is compiled without warnings related to the generic.
However, for client code, it’s easy to ignore them and insert IEventSubscriber waiting for the wrong type:
EventManager manager = ...; IEventSubscriber<Integer> integerSubscriber = ...;
I don’t know how to compile to avoid this scenario, but you can check the general parameter types of instances of IEventSubscriber<T> at runtime if the generic type T was bound to the class at compile time. Consider:
public class ClassA implements IEventSubscriber<String> { ... } public class ClassB<T> implements IEventSubscriber<T> { ... } IEventSubscriber<String> a = new ClassA(); IEventSubscriber<String> b = new ClassB<String>();
In the above example for ClassA , String bound to the T parameter at compile time. All instances of ClassA will have a String for T in IEventSubscriber<T> . But in ClassB String bound to T at runtime. ClassB instances can have any value for T If your IEventSubscriber<T> implementations bind the T parameter at compile time, as noted above, to ClassA , you can get this type at run time as follows:
public <T> boolean subscribe(IEventSubscriber<T> subscriber, Class<T> eventClass) { Class<? extends IEventSubscriber<T>> subscriberClass = subscriber.getClass(); // get generic interfaces implemented by subscriber class for (Type type: subscriberClass.getGenericInterfaces()) { ParameterizedType ptype = (ParameterizedType) type; // is this interface IEventSubscriber? if (IEventSubscriber.class.equals(ptype.getRawType())) { // make sure T matches eventClass if (!ptype.getActualTypeArguments()[0].equals(eventClass)) { throw new ClassCastException("subscriber class does not match eventClass parameter"); } } } CopyOnWriteArraySet<IEventSubscriber> existingSubscribers = subscriptions.putIfAbsent(eventClass, new CopyOnWriteArraySet<IEventSubscriber>()); return existingSubscribers.add(subscriber); }
This will cause the types to be checked when the subscriber is registered using the EventManager , which will make it easier for you to track bad code, and not just if the types will just be checked much later when the event is published. However, it does make some focal reflection and can only check those types if T bound at compile time. If you can trust the code that will send EventManager subscribers, I would just leave the code as it is, because it is much simpler. However, type checking using reflection, as mentioned above, will make you a bit more secure IMO.
One more note, you might want to reorganize the way you initialize your CopyOnWriteArraySet s, because the subscribe method is currently creating a new set for each call, regardless of whether it is needed or not. Try the following:
CopyOnWriteArraySet<IEventSubscriber> existingSubscribers = subscriptions.get(eventClass); if (existingSubscribers == null) { existingSubscribers = subscriptions.putIfAbsent(eventClass, new CopyOnWriteArraySet<IEventSubscriber>()); }
This avoids creating a new CopyOnWriteArraySet each time the method is called, but if you have a race condition and two threads try to install immediately, putIfAbsent will still return the first set created in the second thread, so there is no danger of overwriting it.