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:
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)