Home > database >  Getting confused with generics compile-time error
Getting confused with generics compile-time error

Time:11-03

I am writing an event system for my Java application and I keep getting confused about generics.
Let's say this is EventManager.java:

public class EventManager {
    private final Map<Class<? extends Event>, List<EventHandler<?>>> eventHandlerMap = new HashMap<>();

    public synchronized <T extends Event> void fire(T event) {
        Class<? extends Event> eventClass = event.getClass();
        if (!eventHandlerMap.containsKey(eventClass)) return;

        List<EventHandler<? extends Event>> handlerList =
                eventHandlerMap.get(eventClass);
        for (EventHandler<? extends Event> eventHandler : handlerList) {
            eventHandler.handleEvent(event.getClass().cast(event));
        }
    }
}

Event.java:

public abstract class Event {
}

and EventHandler.java:

public abstract class EventHandler<T extends Event> {
    public abstract void handleEvent(T event);
}

IntelliJ IDEA reports me the following error:Error message

As a temporary mitigation I have added the following method to EventHandler class:

@SuppressWarnings("unchecked")
public final void handleEvent0(Event event) {
    handleEvent((T) event);
}

...and changed the fire method in EventManager:

public synchronized void fire(Event event) {
    Class<? extends Event> eventClass = event.getClass();
    if (!eventHandlerMap.containsKey(eventClass)) return;

    List<EventHandler<? extends Event>> handlerList =
            eventHandlerMap.get(eventClass);
    for (EventHandler<? extends Event> eventHandler : handlerList) {
        eventHandler.handleEvent0(event);
    }
}

This does work however I am wondering what I have done wrong initially and how can I properly fix it. Please help and sorry for my English!

CodePudding user response:

The compiler cannot ensure that eventHandler and event.getClass() are of same type T so it gives the error.

If you can ensure that in eventHandlerMap the key and value types matches you can cast eventHandler to EventHandler<T>.

for (EventHandler<? extends Event> eventHandler : handlerList) {
    @SuppressWarnings("unchecked")
    EventHandler<T> handler = (EventHandler<T>) eventHandler;
    handler.handleEvent(event);
}

Old approach using raw types: DO NOT USE (see comment of Joachim Sauer):

You can remove the generic definition <? extends Event> in your loop's variable eventHandler to get rid of handleEvent0. Then there's no need to cast the event.

for (EventHandler eventHandler : handlerList) {
    eventHandler.handleEvent(event);
}

But this will give you some compilier warnings.

With this edit you also can remove the generic type of fire:

public synchronized void fire(Event event)
  • Related