0

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!

Brongs Gaming
  • 59
  • 1
  • 8

1 Answers1

0

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)
Stefan Warminski
  • 1,845
  • 1
  • 9
  • 18
  • Thank you for the answer. But how should I declare `fire` method in EventManager? `public synchronized void fire(Event event)`, right? (with no generics) – Brongs Gaming Nov 03 '22 at 09:06
  • @BrongsGaming you're right, your edit is now added in the answer – Stefan Warminski Nov 03 '22 at 09:10
  • 2
    This answer suggests using raw types without warning about why they are bad. Please read [What is a raw type and why shouldn't we use it?](https://stackoverflow.com/questions/2770321/what-is-a-raw-type-and-why-shouldnt-we-use-it). This approach basically forgoes *any* type safety provided by generics, so it's really a last escape hatch. – Joachim Sauer Nov 03 '22 at 09:19
  • @BrongsGaming as mentioned by JoachimSauer raw types should not be used. I edited the answer for a new solution. The method's signature is back to the generice version `public synchronized void fire(T event)` – Stefan Warminski Nov 03 '22 at 09:37