javagenericsobserver-patternunchecked

EventSource with Map<Class<? extends Event>, List<EventListener<? extends Event>> without unchecked call


I need to implement Observer pattern with List of listeners per Event class.

I have:

empty interface Event

public interface Event {}

interface for Listeners:

public interface EventListener<T extends Event> {
void handle(T event);

Class<T> getEventClass();}

and event source:

public class EventSource {
private final Map<Class<? extends Event>, List<EventListener<? extends Event>>> listeners = new HashMap<>();

public <T> void subscribe(EventListener<? extends Event> listener) {
    Class<? extends Event> eventClass = listener.getEventClass();
    if (!listeners.containsKey(eventClass)) {
        listeners.put(eventClass, new ArrayList<>());
    }
    listeners.get(eventClass).add(listener);
}

public void unsubscribe(EventListener listener) {
    listeners.remove(listener.getEventClass());
}

public void fire(Event event) {
    for (EventListener listener : listeners.get(event.getClass())) {
        listener.handle(event); //<-- Unchecked call to 'handle(T)' as a member of raw type...
    }
}}

It works, but I have "Unchecked call" warning. How to avoid it?

I tried:

public void fire(Event event) {
    for (EventListener<? extends Event> listener : listeners.get(event.getClass())) {
        listener.handle(event);//<-- compilation error
    }
}

but in this case I have "handle cannot be applied to..." compilation error.

Thanks in advance!


Solution

  • The "unchecked call" warning was reported for a good reason - you used the raw form of EventListener. When you gave a type parameter ? extends Event, it gave the compilation error because the compiler doesn't know which Event the EventListener handles - it only knows that it is a specific type of Event. Likewise, it doesn't know the runtime type of event - it could be any specific type of Event.

    The type must be known for this to make sense. I would make EventSource generic, specifying the type of Event with a bounded generic type parameter.

    class EventSource<E extends Event> {
    

    Many ? extends Event declarations will change to use E, e.g.

    private final Map<Class<? extends Event>, List<EventListener<E>>> listeners = new HashMap<>();
    

    There are many other declarations that will change to use E.

    Now, fire will take an E so that it can be passed to handle:

    public void fire(E event) {
       for (EventListener<E> listener : listeners.get(event.getClass())) {
          listener.handle(event); // Now this compiles
       }
    }